5.7.17版本mysqlbinlog实时拉取的二进制日志不完整的原因分析
问题描述:
同事使用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实时拉取的二进制日志不完整的原因分析的更多相关文章
- 【学习随手记】kubeadm 查看创建集群需要的镜像版本,附拉取镜像脚本
查看创建集群需要的镜像版本 kubeadm config images list [--kubernetes-version <version>] 国内拉取镜像脚本 一般而言,直接使用ku ...
- 用setTimeout 代替 setInterval实时拉取数据
在开发中,我们常常碰到需要定时拉取网站数据,如: setInterval(function(){ $.ajax({ url: 'xx', success: function( response ){ ...
- git day01笔记 常用操作命令 快照 推送 拉取
ansible 批量在远程主机上执行命令或者脚本 git 做版本控制的一个工具 ## git操作命令: 工作区:当前编辑的区域 缓存区:add 之后的区域 本地仓库:commit之后的区域 远程仓 ...
- 【Gitlab】从Gitlab拉取项目+往Gitlab发布项目 【GitLab自定义端口】
1>GIt需要提前安装在本地,本机,自己的电脑,开发环境电脑,IDEA所在的电脑 2>代码仓库:gitlab 3>开发工具:IDEA 4>内网搭建gitlab,访问url: h ...
- Git如何强制拉取一个远程分支到本地分支(转载)
有时候,我们在使用git pull指令想把一个远程分支拉取到本地分支的时候,老是会拉取失败,这一般是因为某种原因,本地分支和远程分支的内容差异无法被git成功识别出来,所以git pull指令什么都不 ...
- git拉取远程分支并创建本地分支和Git中从远程的分支获取最新的版本到本地
git拉取远程分支并创建本地分支 一.查看远程分支 使用如下Git命令查看所有远程分支: git branch -r 二.拉取远程分支并创建本地分支 方法一 使用如下命令: git checkout ...
- git常用操作 配置用户信息、拉取项目、提交代码、分支操作、版本回退...
git常用操作 配置用户信息.拉取项目.提交代码.分支操作.版本回退... /********git 配置用户信息************/ git config --global user.name ...
- 【docker】查看docker镜像的版本号TAG,从远程仓库拉取自己想要版本的镜像
要想查看镜像的版本好TAG,需要在docker hub查看 地址如下:https://hub.docker.com/r/library/ 进入之后,在页面左上角搜索框搜索, 例如搜索redis 搜索完 ...
- git上拉取tag,识别最新tag在此版本上新增tag
通过shell 脚本自动获取最新tag,并输入最新版本后,推到git上 # 拉取分支上现有的tags git fetch --tags echo -e "所有tag列表" git ...
随机推荐
- stand up meeting 12-2
今天因为各位组员组里项目原因没有集中在一起进行stand up meeting.但是士杰和天赋国庆分别对项目进度和前后端的结合进行的沟通. 针对后端部分,天赋完成了GetRankingData API ...
- Linux学习笔记(三)目录和文件都能操作的命令
目录和文件都能操作的命令 rm cp mv rm 英文原意:remove files or directories 功能:删除文件或目录 语法:rm 选项[-fir] 文件或目录 rm -f 强制删除 ...
- vue2.x学习笔记(八)
接着前面的内容:https://www.cnblogs.com/yanggb/p/12577433.html. 列表渲染 vue提供了一个[v-for]指令用于列表渲染(循环). 用[v-for]指令 ...
- 谈谈MySQL的索引
目录 索引 前言 是什么 B树 B+树 B树和B+树结构上异同 有什么用 怎么用 索引 前言 总所周知,数据库查询是数据库的最主要功能之一.我们都希望查询数据的速度能尽可能的快.而支撑这一快速的背后就 ...
- 刷题记录:[V&N2020 公开赛]TimeTravel
题目复现链接:https://buuoj.cn/challenges 参考链接:2020 年 V&N 内部考核赛 WriteUp V&N公开赛2020 writeup httpoxy ...
- ios快捷指令编程尝试
最近,,,啊好几个月了,发现这个ios的快捷指令很好玩 原生就提供了不少功能 用来练习编程思维是十分有用啊...) 其次呢,还可以使用外接的功能对原有的功能进行拓展,比如api借口啊,ssh执行程序啊 ...
- 使用Idea当中的快捷键快速查看继承关系或其图表的两种方法
一.Idea当中有两种方法可以查看继承关系 在Idea当中选中一个类,然后按Ctrl+H,可以快速查看当前所选类的继承关系,如下图: 同样选中一个类,按CTRL+ALT+U,即可生成当前类的继承关 ...
- 给动态ajax添加的元素添加click事件
$(document).on('click','div',function(){alert(1)}); .live()方法也是可以的
- 2019-2020-1 20199328《Linux内核原理与分析》第五周作业
实验要求: 实验步骤: 这里以20号系统调用getpid为例进行实验,该函数的功能为:返回当前进程标识. getpid.c代码: 查看实验结果: 当前进程pid为:31042. 在C语言中编入汇编代码 ...
- scala教程之:可见性规则
文章目录 public Protected private scoped private 和 scoped protected 和java很类似,scala也有自己的可见性规则,不同的是scala只有 ...