MySQL-NonMySQL同步工具源码解读——鉴权与注册

好吧,其实这节是解读一个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,只介绍官方的方式。

wKiom1L5mcGxBi9BAAB9vaQoi0I217.jpg

①以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包。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章