问题描述:

同事使用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. Chrome插件安利!可以一键导出微信读书笔记|支持Markdown等三种格式

    众所周知,微信读书App 是一款非常优秀的阅读类App ,周围也有不少人在用.虽然工作比较忙.但是也没少在上面看书做笔记. 美中不足的是,目前微信读书虽然支持笔记导出,但是提供的是将笔记复制到剪切板, ...

  2. linux之cat 操作

    1.查看或创建 cat 1.txt #如果目录有这个文件则会打开查看,没有则会创建 2.压缩空白 cat 1.txt 我是第一行 我是第二 行 cat -bs 1.txt # 变成 cat 1.txt ...

  3. 2. Git-命令行-删除本地和远程分支

    命令行方式 Git Bash: 切换到要操作的项目文件夹 命令行 : $ cd <ProjectPath> 查看项目的分支们(包括本地和远程) 命令行 : $ git branch -a ...

  4. Xss Game挑战

    前言 最新学习了下xss的更深入的东西,学习了一波浏览器解析机制和XSS向量编码的知识. 这里就些xss的练习题巩固知识 学习的话结合如下两篇文章看,从例子和基础原理层面都有: http://boba ...

  5. 【山外笔记-云原生】《Docker+Kubernetes应用开发与快速上云》读书笔记-2020.04.25(六)

    书名:Docker+Kubernetes应用开发与快速上云 作者:李文强 出版社:机械工业出版社 出版时间:2020-01 ISBN:9787111643012 [山外笔记-云原生]<Docke ...

  6. c++<cstdlib>文件中的函数产生随机数

    C++中没有自带的random函数,要生成随机数就需要用c文件"stdlib.h"里的函数rand()和srand(),不过,由于rand()的内部实现是用线性同余法做的, 所以生 ...

  7. QT 获取可执行程序的路径

    获取到生成.exe目录 QCoreApplication::applicationDirPath(); 获取当前工程目录 QDir::currentPath()

  8. 取 token 并查看 container 信息

    curl -i -k \ -H "Content-Type: application/json" \ -d ' { "auth": { "identi ...

  9. 在java中使用JMH(Java Microbenchmark Harness)做性能测试

    文章目录 使用JMH做性能测试 BenchmarkMode Fork和Warmup State和Scope 在java中使用JMH(Java Microbenchmark Harness)做性能测试 ...

  10. 第八章服务器raid及配置实战

      版本 特点 磁盘个数 可用空间 故障磁盘数 应用环境 RAID0 读写速度快,数据容易丢失 两个 全部 一块 测试,临时性 RAID1 读写速度慢,数据可靠 至少两个,可以2的倍数 总容量的一半 ...