问题描述:

同事使用mysqlbinlog工具的--read-from-remote-server --raw选项,从远程实例实时拉取二进制日志时,发现得到的二进制日志文件大小与远程实例上的源文件大小不相同,并且使用mysqlbinlog解析时会报错。

测试环境版本信息如下:

MySQL版本:5.7.17 log MySQL Community Server (GPL)  通用tar包安装

Mysqlbinlog版本:5.7.17 自带版本,mysqlbinlog Ver 3.4 for linux-glibc2.5 at x86_64

操作系统版本:CentOS Linux release 7.6.1810 (Core)

分析过程:

1、对比拉取到的二进制日志文件、源文件的大小,发现拉取到的二进制日志文件比源文件小,并且该文件大小:7315456/4096,刚好可以整除,源库上进行多次更新后,多次观察,发现该数值都可以被4096整除。

因为Linux块大小是4096 Bytes,所以,我先做个假设:mysqlbinlog工具以4096字节为单位进行写入

[root@192_168_129_128 tmp]# ll /usr/local/mysql-5.7.-linux-glibc2.-x86_64/data/mybinlog. ; ll mybinlog.
-rw-r----- mysql mysql May : /usr/local/mysql-5.7.-linux-glibc2.-x86_64/data/mybinlog.
-rw-r----- root root May : mybinlog.

2、我在8.0.19版本中测试,问题却不能复现。

3、写一个简单的shell脚本,将mysqlbinlog拉取进程放到后台执行,获取到上一步的pid之后,使用pstack命令和strace命令,分别查看该进程的函数调用和系统调用

#!/bin/bash 

nohup mysqlbinlog --read-from-remote-server --host=192.168.129.128 --user=dba --password= --raw --to-last-log binlog. --stop-never &

pid=`ps -ef | grep mysqlbinlog | grep -v 'grep' | awk '{print $2}'`

pstack $pid > pstack.log

strace -f -t -p $pid -o strace.log

pstack命令的输出结果如下,虽然没有直接的线索,但是后续可以利用这些函数栈名,去源码中找线索:

#  0x00007faf58f60a2d in recv () from /lib64/libpthread.so.
# 0x000000000045e9f9 in inline_mysql_socket_recv (flags=, n=, buf=0x1d959d1, mysql_socket=..., src_line=, src_file=0x550918 "/export/home/pb2/build/sb_0-21378219-1480347226.17/mysql-5.7.17/vio/viosocket.c") at /export/home/pb2/build/sb_0--1480347226.17/mysql-5.7./include/mysql/psi/mysql_socket.h:
# vio_read (vio=0x1d90e60, buf=0x1d959d1 "27399199-10542799900-96491182343-85303866742-80196460272-89060617578-51177070778-10421134155;80308169113-21291571753-18876715410-91134905277-85771492482\360j\003", size=) at /export/home/pb2/build/sb_0--1480347226.17/mysql-5.7./vio/viosocket.c:
# 0x0000000000434873 in net_read_raw_loop (count=, net=0x1d8e550) at /export/home/pb2/build/sb_0--1480347226.17/mysql-5.7./sql/net_serv.cc:
# net_read_packet (net=0x1d8e550, complen=0x7fff6d023f88) at /export/home/pb2/build/sb_0--1480347226.17/mysql-5.7./sql/net_serv.cc:
# 0x0000000000434a7c in my_net_read (net=0x1d8e550) at /export/home/pb2/build/sb_0--1480347226.17/mysql-5.7./sql/net_serv.cc:
# 0x000000000043985b in cli_safe_read_with_ok (mysql=0x1d8e550, parse_ok= '\000', is_data_packet=0x0) at /export/home/pb2/build/sb_0--1480347226.17/mysql-5.7./sql-common/client.c:
# 0x0000000000426b36 in dump_remote_log_entries (logname=0x7fff6d0277e9 "binlog.000002", print_event_info=0x7fff6d0250f0) at /export/home/pb2/build/sb_0--1480347226.17/mysql-5.7./client/mysqlbinlog.cc:
# dump_single_log (logname=0x7fff6d0277e9 "binlog.000002", print_event_info=0x7fff6d0250f0) at /export/home/pb2/build/sb_0--1480347226.17/mysql-5.7./client/mysqlbinlog.cc:
# dump_multiple_logs (argc=, argv=<optimized out>) at /export/home/pb2/build/sb_0--1480347226.17/mysql-5.7./client/mysqlbinlog.cc:
# 0x0000000000427821 in main (argc=, argv=0x1d58468) at /export/home/pb2/build/sb_0--1480347226.17/mysql-5.7./client/mysqlbinlog.cc:

strace命令的输出结果如下:

....................省略若干行....................
:: recvfrom(, "6813690-49945547265-44609719076-"..., , , NULL, NULL) =
:: lseek(, , SEEK_CUR) =
:: write(, "553290165-79173089006-0778194955"..., ) =
:: write(, "2\360E\t\0\0p\3\0\0w71247594556-464035229"..., ) =
:: recvfrom(, " \0\0\211\0K\200\266^\20/\201\0\0\37\0\0\0\320d4\0\0\0\21\1\0\0\0\0\0\0"..., , , NULL, NULL) =
:: lseek(, , SEEK_CUR) =
:: lseek(, , SEEK_CUR) =
:: lseek(, , SEEK_CUR) =
:: lseek(, , SEEK_CUR) =
:: lseek(, , SEEK_CUR) =
:: lseek(, , SEEK_CUR) =
:: lseek(, , SEEK_CUR) =
:: lseek(, , SEEK_CUR) =
:: lseek(, , SEEK_CUR) =
:: lseek(, , SEEK_CUR) =
:: lseek(, , SEEK_CUR) =
:: lseek(, , SEEK_CUR) =
:: lseek(, , SEEK_CUR) =
:: lseek(, , SEEK_CUR) =
:: lseek(, , SEEK_CUR) =
:: lseek(, , SEEK_CUR) =
:: lseek(, , SEEK_CUR) =
:: lseek(, , SEEK_CUR) =
:: lseek(, , SEEK_CUR) =
:: lseek(, , SEEK_CUR) =
:: lseek(, , SEEK_CUR) =
:: lseek(, , SEEK_CUR) =
:: recvfrom(,

strace到这里就没有后续的输出了。

其中,recvfrom系统调用,是经socket接收数据;lseek是一个用于改变读写一个文件时读写指针位置的一个系统调用;write把buf中nbyte写入文件描述符handle所指的文档,成功时返回写的字节数,错误时返回-1.

分析strace日志中的多条write记录,发现每次write的写入字节数都是4096,看起来与前面的假设吻合。

但是,每达到4096字节才写入磁盘,那么拉取到的binlog几乎不可能与源库一致。而mysqlbinlog的help显示,--read-from-remote-server参数,实质上是调用MySQL Server的BINLOG-DUMP-NON-GTIDS接口,远程实例应该是实时发送binlog events才对。

-R, --read-from-remote-server
Read binary logs from a MySQL server. This is an alias
for read-from-remote-master=BINLOG-DUMP-NON-GTIDS.

好吧,从pstack日志中得到的函数名,我们去源码中逐个查看,最终与本问题相关的是client/mysqlbinlog.cc中的dump_remote_log_entries()函数,如下:

/**
Requests binlog dump from a remote server and prints the events it
receives. @param[in,out] print_event_info Parameters and context state
determining how to print.
@param[in] logname Name of input binlog. @retval ERROR_STOP An error occurred - the program should terminate.
@retval OK_CONTINUE No error, the program should continue.
@retval OK_STOP No error, but the end of the specified range of
events to process has been reached and the program should terminate.
*/
static Exit_status dump_remote_log_entries(PRINT_EVENT_INFO *print_event_info,
const char* logname)
{
....................省略若干行....................
for (;;)
{
const char *error_msg= NULL;
Log_event *ev= NULL;
Log_event_type type= binary_log::UNKNOWN_EVENT; len= cli_safe_read(mysql, NULL);
if (len == packet_error)
{
error("Got error reading packet from server: %s", mysql_error(mysql));
DBUG_RETURN(ERROR_STOP);
}
if (len < && net->read_pos[] == )
break; // end of data
DBUG_PRINT("info",( "len: %lu net->read_pos[5]: %d\n",
len, net->read_pos[]));
/*
In raw mode We only need the full event details if it is a
ROTATE_EVENT or FORMAT_DESCRIPTION_EVENT
*/ ....................省略若干行....................
if (raw_mode || (type != binary_log::LOAD_EVENT))
{ ....................省略若干行....................
if (raw_mode)
{
DBUG_EXECUTE_IF("simulate_result_file_write_error",
DBUG_SET("+d,simulate_fwrite_error"););
if (my_fwrite(result_file, net->read_pos + , len - , MYF(MY_NABP)))
/*可以看到,cli_safe_read()方法读取到的binlog event,都会调用my_fwrite函数进行写入,my_fwrite是对fwrite()函数的封装
这里并没有以4096字节为单位写入,而是读多少就写入多少
这就无法解释了,代码逻辑显示每次拿到数据之后,都会写入磁盘,为什么实际上却不是呢?*/
{
error("Could not write into log file '%s'", log_file_name);
retval= ERROR_STOP;
}
if (ev)
reset_temp_buf_and_delete(ev);
}
....................省略若干行.................... if (retval != OK_CONTINUE)
DBUG_RETURN(retval);
}
else
{
....................省略若干行....................
old_off+= len-;
} DBUG_RETURN(OK_CONTINUE);
}

请看我用中文注释在上述源码中的分析。

代码逻辑没有问题,那就有可能是BUG了。。。

结论:

通过上述分析,怀疑遇到了BUG,于是去官方的Release Notes中查找,最终在https://dev.mysql.com/doc/relnotes/mysql/5.7/en/news-5-7-19.html 中找到如下说明:

Replication:

mysqlbinlog, if invoked with the --raw option, does not flush the output file until the process terminates. But if also invoked with the --stop-never option,

the process never terminates, thus nothing is ever written to the output file. Now the output is flushed after each event. (Bug #24609402)

但是这个BUG在BUG system中找不到,应该是需要有权限的MOS账号才能查看。

5.7.17版本mysqlbinlog实时拉取的二进制日志不完整的原因分析的更多相关文章

  1. 【学习随手记】kubeadm 查看创建集群需要的镜像版本,附拉取镜像脚本

    查看创建集群需要的镜像版本 kubeadm config images list [--kubernetes-version <version>] 国内拉取镜像脚本 一般而言,直接使用ku ...

  2. 用setTimeout 代替 setInterval实时拉取数据

    在开发中,我们常常碰到需要定时拉取网站数据,如: setInterval(function(){ $.ajax({ url: 'xx', success: function( response ){ ...

  3. git day01笔记 常用操作命令 快照 推送 拉取

    ansible 批量在远程主机上执行命令或者脚本 git   做版本控制的一个工具 ## git操作命令: 工作区:当前编辑的区域 缓存区:add 之后的区域 本地仓库:commit之后的区域 远程仓 ...

  4. 【Gitlab】从Gitlab拉取项目+往Gitlab发布项目 【GitLab自定义端口】

    1>GIt需要提前安装在本地,本机,自己的电脑,开发环境电脑,IDEA所在的电脑 2>代码仓库:gitlab 3>开发工具:IDEA 4>内网搭建gitlab,访问url: h ...

  5. Git如何强制拉取一个远程分支到本地分支(转载)

    有时候,我们在使用git pull指令想把一个远程分支拉取到本地分支的时候,老是会拉取失败,这一般是因为某种原因,本地分支和远程分支的内容差异无法被git成功识别出来,所以git pull指令什么都不 ...

  6. git拉取远程分支并创建本地分支和Git中从远程的分支获取最新的版本到本地

    git拉取远程分支并创建本地分支 一.查看远程分支 使用如下Git命令查看所有远程分支: git branch -r 二.拉取远程分支并创建本地分支 方法一 使用如下命令: git checkout ...

  7. git常用操作 配置用户信息、拉取项目、提交代码、分支操作、版本回退...

    git常用操作 配置用户信息.拉取项目.提交代码.分支操作.版本回退... /********git 配置用户信息************/ git config --global user.name ...

  8. 【docker】查看docker镜像的版本号TAG,从远程仓库拉取自己想要版本的镜像

    要想查看镜像的版本好TAG,需要在docker hub查看 地址如下:https://hub.docker.com/r/library/ 进入之后,在页面左上角搜索框搜索, 例如搜索redis 搜索完 ...

  9. git上拉取tag,识别最新tag在此版本上新增tag

    通过shell 脚本自动获取最新tag,并输入最新版本后,推到git上 # 拉取分支上现有的tags git fetch --tags echo -e "所有tag列表" git ...

随机推荐

  1. D. Points in rectangle

    D. Points in rectangle 单点时限: 2.0 sec 内存限制: 512 MB 在二维平面中有一个矩形,它的四个坐标点分别为(0,a),(a,0),(n,n−a),(n−a,n). ...

  2. Linux常见提权

    常见的linux提权 内核漏洞提权 查看发行版 cat /etc/issue cat /etc/*-release 查看内核版本 uname -a 查看已经安装的程序 dpkg -l rpm -qa ...

  3. Springboot:logback日志管理(九)

    Springboot默认使用的日志框架就是logback 创建自定义的logback-spring.xml放在resources类目录下即可 logback-spring.xml: <?xml ...

  4. 模拟HTTP请求调用controller

    原文参考本人的简书:https://www.jianshu.com/p/0221edbe1598 MockMvc实现了对Http请求的模拟,能够直接使用网络的形式,转换到Controller调用,这样 ...

  5. C# 基础知识系列- 14 IO篇之入门IO

    0. 前言 在之前的章节中,大致介绍了C#中的一些基本概念.这篇我们将介绍一下C#的I/O操作,这将也是一个小连续剧.这是第一集,我们先来简单了解一下C#中的I/O框架. 1. 什么是I/O I/O ...

  6. RxJava--Buffer,GroupBy 对比

    Buffer 设定收集n个元素为一组,以下方代码为例,三个为一组,则当组满三个元素时,返回一次List数据 没组满三个元素时,如果调用onComplete,直接发送剩余元素,没调用onComplete ...

  7. NodeJS反向代理websocket

    如需转载请标明出处:http://blog.csdn.net/itas109QQ技术交流群:129518033 文章目录NodeJS反向代理websocket@[toc]前言代码相关问题:1.http ...

  8. C++ 重载运算符 继承 多态 (超详细)

    (一)重载运算符: (1)声明与定义格式 一般是类内声明,类外定义,虽然可以在类内定义,但 写前面堆一堆不好看!!! 类内声明: class Demo { 返回值类型 operator 运算符(形参表 ...

  9. Redis为什么是单线程的

    一.前言   最近在学习Redis,这篇文章就来简单聊聊一道常考的面试题--Redis为什么是单线程的.废话不多说,直接开始吧. 二.正文 2.1 为什么需要多线程   首先,现在的CPU一般都是由多 ...

  10. TensorFlow实现时间序列预测

    常常会碰到各种各样时间序列预测问题,如商场人流量的预测.商品价格的预测.股价的预测,等等.TensorFlow新引入了一个TensorFlow Time Series库(以下简称为TFTS),它可以帮 ...