好吧,其实这节是解读一个MySQL的一个事件捕获工具――MySQL replication。MySQL的主从同步就是通过向Binlog写入事件,由主库向从库传递,来完成的。那么如果非MySQL的应用也想从主库做同步,就需要自行解决获取事件和解析的问题。这个工具就是做这件事的,其中有个例子就是把数据库事件写入Lucenet索引。
项目主页详见:https://launchpad.net/mysql-replication-listener。全C++风格编写,网络传输使用的Boost的asio库,所有数据写入和读取基本上都采用流式完成。看着很不爽,几次想改成传统的结构体封装内存结构的方式,自认为对MySQL的Binlog文档已经读了很多,但是提到细节的理解还是不够深刻,所以一直没有完成。虽然文档和MySQL源码是了解协议的最好方式,可是前者内容较多,每次都是抓不到重点,后者尝试着读过几天,(昨天才发现mysqlbinlog也能模拟为从库方式向主库同步,获取时间信息,要是之前就发现就好了,还是自己能力不够啊!)和别的模块耦合比较严重,有种牵一发而动全身的感觉(这个成语用在这里是不对的,有种迅雷不及掩耳盗铃儿响叮当之势),真正应用到自己写的应用还是感觉有难度。所以还是先从这个开源小工具入手吧。
这个故事,就先从程序启动开始。
工具以库的方式提供给使用者,接口是Binlog类。首先需要创建这样的对象,在这时根据参数确定是解析本地的Binlog文件还是以找主库去同步去。这里我们只讨论后者,因此Binarary_log_driver使用的是Binlog_tcp_driver。
创建过后需要调用connect方法。确定数据库的标识为username password ip:port。因此这几个参数都要传进去(端口可以不指定,MySQL默认是3306,要和主库一致)。这个方法执行三个步骤:鉴权--确定首次读取的偏移--开始循环读取。
每个数据包,头部都由固定的4个字节开始。前三个字节是数据包长度,采用小端序,第四个字节是包的序号,一般是自增。第四个字节同包的正文(文档中称其为payload),一起算到包的总长度中去,而前三个字节不算在内。后文介绍的包格式,没有包括固定的这前4个字节。
鉴权(sync_connect_and_authenticate)
MySQL的鉴权分为Plain text和SSL两种,对于局域网的话,前者就可以了,实现也比较简单。整个流程可以用下图来表示。
认证协议这块还要多说两句。历史上,MySQL有两种鉴权协议。4.0以及以前的版本使用Old Password Authentication。
从MySQL4.1开始,使用 Secure Password Authentication
作为密码的签名方式。旧的加密采用8Byte的密钥,签名结果也是采用8Byte。这种加密强度很差,其后更换了加密方式,即20byte的密钥,20Byte的结果。
由于使用的MySQL是5.5+,这里忽略旧的加密方式以及加密切换,也不使用鉴权相关的plug-in,只介绍官方的方式。
①以io_service结构体建立到MySQL的tcp连接,主要是通过域名解析ip,逐个尝试每个ip,直到连接建立好。
②读取MySQL发来的握手包,版本一般是V10。格式如下,
1 [0a] protocol version string[NUL] server version 4 connection id string[8] auth-plugin-data-part-1 1 [00] filler 2 capability flags (lower 2 bytes) if more data in the packet: 1 character set 2 status flags 2 capability flags (upper 2 bytes) if capabilities & CLIENT_PLUGIN_AUTH { 1 length of auth-plugin-data } else { 1 [00] } string[10] reserved (all [00]) if capabilities & CLIENT_SECURE_CONNECTION { string[$len] auth-plugin-data-part-2 ($len=MAX(13, length of auth-plugin-data - 8)) if capabilities & CLIENT_PLUGIN_AUTH { string[NUL] auth-plugin name }
整个反序列化解析的过程在函数proto_get_handshake_package里面,需要注意的是,这里的capability flags非常有用,此后的解析都需要他。特别是其中的CLIENT_PROTOCOL_41,非常重要。其他位可以参见https://dev.mysql.com/doc/internals/en/capability-flags.html。可以说,MySQL4.1是新旧协议上的重大分水岭。
③发起认证(authenticate)
类似的,认证包也有众多格式。对于5.5+的MySQL而言,一般是采用Handshake41版,格式如下。
4 capability flags, CLIENT_PROTOCOL_41 always set 4 max-packet size 1 character set string[23] reserved (all [0]) string[NUL] username if capabilities & CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA { lenenc-int length of auth-response string[n] auth-response } else if capabilities & CLIENT_SECURE_CONNECTION { 1 length of auth-response string[n] auth-response } else { string[NUL] auth-response } if capabilities & CLIENT_CONNECT_WITH_DB { string[NUL] database } if capabilities & CLIENT_PLUGIN_AUTH { string[NUL] auth plugin name } if capabilities & CLIENT_CONNECT_ATTRS { lenenc-int length of all key-values lenenc-str key lenenc-str value if-more data in 'length of all key-values', more keys and value pairs }
最终的密码是将裸密码经过三次SHA-1计算而得,Key用到了先前的两个scramble_buff。prot_client_flags由客户端自行选择,为了实现简单,最好不要启用SSL、压缩、分段事件等复杂协议。最大报文长度传0xffffff。
读服务端传来的认证结果。
这里涉及MySQL包的主库通用返回类型,主要是三种:OK、ERROR、EOF。对应的格式分别是:
OK
1 [00] the OK header lenenc-int affected rows lenenc-int last-insert-id if capabilities & CLIENT_PROTOCOL_41 { 2 status_flags 2 warnings } elseif capabilities & CLIENT_TRANSACTIONS { 2 status_flags } string[EOF] info
ERROR
1 [ff] the ERR header 2 error code if capabilities & CLIENT_PROTOCOL_41 { string[1] '#' the sql-state marker string[5] sql-state } string[EOF] error-message
EOF
1 [fe] the EOF header if capabilities & CLIENT_PROTOCOL_41 { 2 warning count 2 status flags }
④向主库注册为从库
1 [14] COM_REGISTER_SLAVE 4 server-id 1 slaves hostname length string[$len] slaves hostname 1 slaves user len string[$len] slaves user 1 slaves password len string[$len] slaves password 2 slaves mysql-port 4 replication rank 4 master-id
这里使用的命令是COM_REGISTER_SLAVE。MySQL暴露了一系列的COM_命令,其中有一部分称之为TEXT_PROTOCOL。使用这个COM_REGISTER_SLAVE要注意,任何从库的ID都不要相同,否则会出现同步问题(因为我们将不是用GTID的同步方式)。里面有个很奇怪的字段叫replication rank,不知道是做什么的,目前的MySQL只是简单的忽略这个字段,所以传什么都OK。
随后依旧是需要向③一样接收并解析OK包。