binlog是什么?
binlog是一个非常重要的日志,是mysql server层的日志, 记录用户的各种操作(changes). 默认启用.
binlog有啥用?
binlog怎么校验是否损坏?
这个是本文重要讨论的, 虽然mysqlbinlog
的--verify-binlog-checksum
选项就能校验binlog是否损坏(其实是写这个工具之前没发现这个参数....).
binlog损坏了怎么办?
凉拌
在讨论校验原理之前, 我们先简单看看binlog的结构:
对象 | 大小 | 描述 |
---|---|---|
timestamp | 4 | 时间戳 |
event_type | 1 | event的类型 |
server_id | 4 | serverid |
event_size | 4 | 这个event大小(含event_header&checksum) |
log_pos | 4 | 结束位置的偏移量 |
flags | 2 | 一些flag |
这部分信息之前都是讲过的, 有兴趣的可以往前面翻翻:https://www.modb.pro/topic/625137
在FORMAT_DESCRIPTION_EVENT中, 最后的信息是checksum_alg, 表示这个binlog是否有checksum位. 当然前提是参数binlog_checksum
的值为CRC32才行. 如果binlog_checksum
未设置校验的话, 是无法校验Binlog的. 我们这里就只讨论存在校验的情况.
有些情况可能会关闭binlog校验, 比如MGR
既然要校验, 那么肯定得每个event都校验,比较不可能文件写完之后才校验,那样就没得校验的意义了. 由于是server层实现的, 校验的算法通常就是crc32 (innodb使用的是crc32c).
那么这个校验值应该存储在哪呢? 最简单的就是存储在event结尾出. 出于兼容性考虑, event_header记录的大小/pos应该包含这4字节. 于是得到如下结构:
我们要校验的时候, 也只需要将event_header+event_body的crc32校验值和记录的crc32校验值比较即可确定event是否损坏.
既然知道该校验信息了, 那么就来设计个binlog的校验工具吧.
选语言: 我们使用python编写, 这样开发效率最高, 而校验主要是涉及到文件读取和crc32计算,这2者在编程语言之间差距不大(有时候感觉还挺快的)
兼容性: 使用者可能是python2或者python3环境, 所以我们需要考虑py2和py3的兼容性. 使用者可能是5.7, 8.0, 8.4环境, 所以还需要考虑mysql的兼容性. 还得同时支持binlog和relay log(就4字节的差)
使用: 由于功能单一, 不需要复杂的参数, 所以直接使用位置参数即可. 而使用者可能还想一次性校验多个文件, 所以还得考虑多个文件的情况, 而且文件可能有不存在的情况.
校验: 遇到坏的event之后, 就不应该继续校验了, 毕竟无法确定event的哪部分是损坏的, 也就无法确定下一个event的位置
说得比较简洁, 有些问题只有实际写的时候才会遇到, 比如: py2使用binascii.crc32校验的结果是有符号的(范围-2**31, 2**31-1
), 而py3使用binascii.crc32校验的结果是无符号的(范围0, 2**32-1
), 这种问题很多情况发现不了, 只有足够多的数据量(或者运气好)才能发现. 当然解决办法比较简单, 直接将结果&0xffffffff
即可变成无符号的整数
我们还是来看看实际效果吧:
用法:
python3 check_binlog_v2.py /tmp/m3314.000019
正常情况: 无输出
15:11:40 [root@ddcw21 ei]#python3 check_binlog_v2.py /tmp/m3314.000019
15:11:43 [root@ddcw21 ei]#
有坏event的情况:
15:38:26 [root@ddcw21 ei]#python3 check_binlog_v2.py /tmp/m3314.000019_bad
/tmp/m3314.000019_bad have bad event at: 316
多文件的情况
15:12:51 [root@ddcw21 ei]#python3 check_binlog_v2.py /tmp/m3314.000019*
/tmp/m3314.000019_bad have bad event at: 316
/tmp/m3314.000019_bad2 event_header corruption!!! current offset:0
python2的情况:
15:13:25 [root@ddcw21 ei]#python2 check_binlog_v2.py /tmp/m3314.000019*
/tmp/m3314.000019_bad have bad event at: 316
/tmp/m3314.000019_bad2 event_header corruption!!! current offset:0
无校验值的情况:
15:25:19 [root@ddcw21 ei]#python2 check_binlog_v2.py /data/mysql_3314/mysqllog/binlog/m3314.000035
/data/mysql_3314/mysqllog/binlog/m3314.000035 have not binlog_checksum
mysql 5.7的环境
15:40:14 [root@ddcw21 ei]#ls -ahl /data/mysql_3308/mysqllog/binlog/m3308.001098
-rw-r----- 1 mysql mysql 77M Apr 30 18:12 /data/mysql_3308/mysqllog/binlog/m3308.001098
15:40:21 [root@ddcw21 ei]#
15:40:22 [root@ddcw21 ei]#time python3 check_binlog_v2.py /data/mysql_3308/mysqllog/binlog/m3308.001098
real 0m0.149s
user 0m0.114s
sys 0m0.035s
mysqlbinlog的校验(得去掉stdout的正常信息, 不然不方便看...)
15:43:59 [root@ddcw21 ei]#mysqlbinlog --verify-binlog-checksum /tmp/m3314.000019_bad >/dev/null
ERROR: Could not read entry at offset 316: Error in log format or read error 1.
ERROR: Event crc check failed! Most likely there is event corruption.
--verify-binlog-checksum
), 不过之前没有发现而已, 因为之前解析的时候有问题会直接报错的, 而这次没有报错出来, 就以为没得这个功能. 于是就重新写了一版(所以是v2)参考:
https://dev.mysql.com/doc/refman/8.0/en/binary-log.html
https://dev.mysql.com/doc/refman/8.0/en/group-replication-limitations.html
https://docs.python.org/zh-cn/2.7/library/binascii.html#module-binascii
附源码:
下载地址:https://github.com/ddcw/ddcw/blob/master/python/check_binlog_v2.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# check binlog/relaylog, like: mysqlbinlog --verify-binlog-checksum
import os
import sys
import struct
import binascii
F_EVENT_HEADER = struct.Struct("<LBLLLH")
F_CRC32 = struct.Struct("<L")
def help():
sys.stdout.write("usage:\n\tpython "+str(sys.argv[0])+" mysql-bin.00000n\n")
sys.exit(1)
def get_filename():
if len(sys.argv) == 1:
sys.stdout.write("need a filename at least\n")
help()
filename = []
for x in range(1,len(sys.argv)):
fname = str(sys.argv[x])
if not os.path.exists(fname):
sys.stdout.write("filename: "+fname+" is not exists\n")
help()
filename.append(fname)
return filename
def first_event_check(bdata):
binlog_version,mysql_version,create_timestamp,event_header_length = struct.unpack('<H50sLB',bdata[:57])
mysql_version = mysql_version.decode()
offset = 57
if mysql_version[:1] == "5":
offset += 38
elif mysql_version[:4] == "8.4.":
offset += 43
elif mysql_version[:1] == "8":
offset += 41
else:
sys.stdout.write("donot support version:"+mysql_version+"\n")
help()
event_post_header_len = bdata[57:offset]
return True if struct.unpack('<B',bdata[offset:offset+1])[0] else False
def read_event(f,filename):
start_offset = f.tell()
event_header = f.read(19)
if event_header == b'':
return 0,b'',b''
if len(event_header) != 19:
sys.stdout.write(filename+" event_header corruption!!! current offset:"+str(start_offset)+"\n")
sys.exit(1)
timestamp,event_type,server_id,event_size,log_pos,flags = F_EVENT_HEADER.unpack(event_header)
if event_size < 19:
sys.stdout.write(filename+" event_header corruption!!! current offset:"+str(start_offset)+"\n")
sys.exit(2)
event_body = f.read(event_size-19)
if len(event_body) != event_size-19:
sys.stdout.write(filename+" event_body corruption!!! current offset:"+str(start_offset)+"\n")
sys.exit(3)
return start_offset,event_header,event_body
if __name__ == "__main__":
for filename in get_filename():
with open(filename,'rb') as f:
if f.read(4) != b'\xfebin':
f.seek(0,0) # relay log
start_offset,event_header,event_body = read_event(f,filename)
if not first_event_check(event_body):
sys.stdout.write(filename+" have not binlog_checksum\n")
break
while True:
start_offset,event_header,event_body = read_event(f,filename)
if len(event_body) == 4: # STOP_EVENT
break
if event_header == b'': # finish (ROTATE_EVENT)
break
crc32_v1 = F_CRC32.unpack(event_body[-4:])[0]
crc32_v2 = binascii.crc32(event_header+event_body[:-4]) & 0xffffffff # for py2
if crc32_v1 != crc32_v2:
sys.stdout.write(filename+" have bad event at: "+str(start_offset)+" \n")
break
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。