谈谈关于mysql MHA的理解

mysql一般采用一主多从的部署方式,保障数据的可靠性,另外DBA常用MHA实现自动的主从切换。

我们知道,主负责写入,从负责读取,当主宕机后我们需要将一个从库提升为主库,并让其他剩余从库同步这个新的主库,这个过程说说简单,但是具体怎么去做,原理是什么,懂的人就少之又少了。

这两天抽空看了一些相关的介绍,算是有了一个基本的理解,于是记录在这里,继续阅读前请先阅读这篇博客,无论能否能够理解,请先全部读完。

谈谈我的理解

binlog:

  • master上执行的每一个SQL作为一行记录写到文件中,称为binlog。
  • 文件越写越大,所以需要切换文件,因此出现了binlog-0,binlog-1,binlog-2…。
  • master在另外一个文件中,保存当前正在写的是binlog-x文件,文件内偏移量offset。
  • 默认master先将binlog写入磁盘,由slave异步的增量拉取binlog。

看看,binlog文件长这样,里面每一行记录都有自己所在的binlog文件名,偏移量,SQL语句:

relay-log:

  • slave从master同步数据,因此通过socket连接master逐行的读取binlog里的内容。
  • 读取过来的一行SQL并不是直接执行,而是先写到本地文件中,其格式和binlog完全相同,也就是上面那个表格在master和slave上一模一样,这就叫做relay-log-y(y是自增的下标)。
  • slave会在master-info.log文件中记录当前拷贝master的进度:binlog-x文件,binlog文件内偏移量offset。
  • 注意,存储在master的binlog和拷贝到slave并写入的relay-log只是存储的数据内容相同,文件名和记录偏移量没有直接联系。

slave上的binlog:

  • slave上有一个线程,会读取relay-log-y里的SQL执行。
  • 每执行完一个SQL,就会在另外一个文件中(relay-info.log)更新当前已经执行到了relaylog-y文件,文件内偏移量offset。
  • 先执行SQL再更新偏移量的做法,如果中途异常可能导致SQL执行了偏移量没更新,那么下次恢复执行就会导致SQL重复执行了一次,这也是mysql 5.6之后支持通过一个mysql table保存y和offset,并和SQL放在一个事务里来保障原子。
  • relay-log被重放后,其实会和master执行SQL语句一样,在slave上也产生SQL执行的binlog日志文件。

主从切换:

  • 每个slave由于异步拉取binlog的原因,relay-log通常会晚于(少于)master的binlog一些记录。
  • 因为上述原因,slave与master之间数据不一致,不同slave之间数据落后的程度也不一致。
  • 上述情况并非异常,是一个常态。

假设master所在物理机彻底报废,再也不可访问:

  • 因为master机器挂了,那么slave与master之间的数据差异无法靠同步来弥补了,只能丢弃这一部分数据。
  • slave之间数据落后程度不同,这意味着有的slave的relay-log多一点,有的relay-log少一点,总之大家看到的数据不一致。
  • 为了最大程度挽救数据,我们等待所有slave上的relay-log执行完毕,然后看一下每个slave上的master-info.log(记录了拷贝master binlog的最后偏移量),选最新的那个slave作为数据恢复的标杆,将它拥有而其他slave因为拷贝落后而没有的relaylog数据,拷贝到其他slave上。
  • 每个slave将缺失的那段relaylog重放执行,最终所有slave数据是完全一致的。
  • 此后,可以选择任意一个slave作为新的master,让它的binlog清空从头开始,其他slave同步新master的binlog,即完成了一次主从切换。

那么,怎么计算标杆slave和其他落后的slave之间的relaylog差异呢?并不复杂,假设slave-1上的relaylog是这样:

因此,它的master-info.log文件记录的是:mysql-bin.000011,357。

查看slave-2的master-info.log,记录的是:mysql-bin.000011,293。

那么要计算slave-1比slave-2的relaylog多了哪些,只需要顺序扫描slave-1的relaylog文件,并找到mysql-bin.000011,293这一行记录,那么从这之后开始的relaylog都应该拷贝到slave-2,这样slave-2重放这段relaylog就可以和slave-1数据一致了。

重放的方法并不是很复杂,利用mysqlbinlog工具可以解析binlog格式(relaylog和binlog格式一样)的文件,将指定偏移量之后的内容转换成SQL语句,直接由mysql执行即可。

假设master进程退出,物理机还可以访问:

  • 除了上述将slave之间的差异抹平之外,还可以再次访问master的物理机。
  • 获取master的binlog文件偏移量,与抹平后的relaylog偏移量之间的差异数据,得到一段diff的binlog。
  • 将这段diff的binlog应用到所有slave上重放SQL,那么所有slave的数据将和master完全一致,数据一条也没丢。

反思binlog

上面抹平slave之间数据差异的办法,是等待所有slave的relaylog执行后,取最大的relaylog计算与其他slave的差异,依据的是每个slave的master-info.log记录的偏移量,与relaylog日志行中的记录的偏移量比较,最终找到的。

那么,为啥不等待所有slave的relaylog重放完并生成binlog后,直接基于binlog进行抹平呢?感觉也差不了多少啊。

这是因为当slave重放relaylog之后并再次写入到binlog的时候,与master的binlog偏移量之间的关联就丢失了,slave-1上的binlog和slave-2上的binlog文件并不一样,举个例子:slave-2在2017-02-13全量拷贝了master的数据并开始slave master,而slave-1在2017-02-25全量拷贝了master的数据并开始slave,它俩增量同步的数据不同,落地的binlog数量必然不同,两者之间的偏移量也就无从关联了,那么又怎么能计算出2个slave的binlog diff呢?

关于GTID

这是mysql 5.6之后出现的,会为每一条SQL生成全局自增唯一的ID标识,这个SQL在master上执行写入binlog中会携带进去,然后binlog同步到slave并写入到relaylog的时候当然也会记录在其中,最终slave重放relay-log的时候又会将这个GTID写入到binlog中。

既然slave的binlog里都带上这个从master来的GTID了,那么基于slave之间的binlog计算差异就很简单了:先看看哪个slave的最后GTID最大,就以它的binlog作为标本,其他slave的最后GTID与这个GTID之间的binlog就是diff,拷贝到各个slave上重放(转成SQL执行)。

之后的事情就没有区别了,所有slave都抹平了,选择任意一个slave提成master,其他slave同步它即可。

关于半同步

默认master与slave之间是基于binlog的异步传输,因此master宕机后所有slave都可能落后数据。

半同步的意思是master确保每一条binlog至少传输给1个slave后再返回给客户端,因此当master宕机后,我们在所有slave中找到的最新偏移量一定和master相同,数据一条也不会丢。

关于MHA

这个项目是用来实现mysql自动主从切换的,整个实现原理就如同我上面所说的一切,它既支持基于relaylog也支持GTID,完全取决于mysql的版本和配置。

为了不影响客户端访问,因此一般基于VIP飘逸实现master的访问地址不变。

 

我后面有时间会动手部署一下mysql主从和mha,欢迎交流。

 

如果文章帮助您解决了工作难题,您可以帮我点击屏幕上的任意广告,或者赞助少量费用来支持我的持续创作,谢谢~