天问

微信高可用分布式数据库PhxSQL设计与实现

作者丨陈俊超

责编丨卢凯



本文详细描述了PhxSQL的设计与实现。从MySQL的容灾缺陷开始讲起,接着阐述实现高可用强一致的思路,然后具体分析每个实现环节要注意的要点和解决方案,最后展示了PhxSQL在容灾和性能上的成果。


设计背景

互联网应用中账号和金融类关键系统要求和强调强一致性及高可用性。当面临机器损坏、网络分区、主备手工或者自动切换时,传统的MySQL主备难以保证强一致性和高可用性。PhxSQL将MySQL集群构建在一致性完善的Paxos协议基础上,保证了集群内MySQL机器之间数据的强一致性和整个集群的高可用性。

原生MySQL的容灾缺陷

MySQL容灾方案


MySQL有两种常见的复制方案,异步复制和半同步复制。


1. 异步复制方案


Master对数据进行commit操作后再将数据异步复制到Slave。
但数据无法保证成功复制,也就无法保证MySQL主备间的数据一致性,如图1所示。


图1  MySQL异步复制流程

2. 半同步复制方案 


Master对数据进行commit操作前将数据复制到Slave,确认复制成功后再对数据进行commit操作。 


绝大多数情况下,半同步复制能保证MySQL主备间的数据一致性,如图2所示。 


图2  MySQL半同步复制流程

MySQL重启流程


半同步方案中的“半”是指Master在等待Slave的ACK失败时将退化成异步复制。同时,MySQL在重启时也不会执行半同步复制。


如图3中的id(Gtid)=101数据是Master机器中新写入到Binlog File的Binlog数据。但Master在复制数据到Slave的过程中MySQL宕机导致复制失败。MySQL重启时,数据(id=101)会被直接进行commit操作,随后再将数据异步复制到Slave。(下文将已经写入到Binlog File但未进行commit操作的数据(id=101)称为Pending Binlog。)



图3  MySQL重启时直接提交Pending Binlog

该情况下MySQL容易出现Master-Slave之间数据不一致的情况,官方也描述了该问题。


  1. http://bugs.mysql.com/bug.php?id=80395

  2. https://mariadb.atlassian.net/browse/MDEV-162


MySQL重启缺陷


下面将解释MySQL在重启时不执行半同步会产生数据不一致的原因。


当对上述例子中的Pending Binlog(id=101)进行复制时Master宕机导致复制失败,随后Slave1切换成新Master并开始提供服务(写入id=201的数据)。此后,当旧Master重启时,Pending Binlog(id=101)不会被重新进行复制而直接进行commit操作,从而导致旧Master比新Master多了一条数据,旧Master无法成为新Master的Slave,需要人工处理掉这条数据之后,才能让旧Master作为Slave提供服务,如图4所示。 



图4  MySQL重启缺陷导致主备数据不一致

上述case只对旧Master的数据造成影响,不会使得MySQL Client读取到错误数据。但当Master连续出现两次宕机后产生Master切换,两次宕机间隔较短使得Pending Binlog未能及时复制到Slave,且期间有查询请求时(Master宕机→Master重启→查询数据→Master宕机→Master切换),MySQL Client会产生如图5所示的幻读(两次读到的结果不一致)。 


 

图5  MySQL重启缺陷导致Client产生幻读

MySQL Client分裂


当Master出现故障且产生Master切换时,由于原生MySQL缺乏调用端的通知/重定向机制,使得不同的Client可能访问不同的Master,导致数据的错误写入和读取,如图6所示。 



图6  MySQL进行Master导致Client端分裂

MySQL缺乏自动选主机制


由于半同步复制不需要等待所有Slave的ACK,因此当Master出现故障时,需要选有最新Binlog的Slave为新的Master;而MySQL并没有内置这个选主机制,如图7所示。 



图7  MySQL缺少自动选主机制

MySQL的容灾缺陷总结


MySQL在容灾方面存在的问题:


  1. Master切换时主备数据不能保证一致:Master重启并切换可能导致MySQL主备间数据不一致。Master重启并切换可能导致MySQL Client产生幻读。

  2. 原生MySQL缺乏高可用机制:Master切换导致调用端分裂。缺乏自动选主机制。


对于原生MySQL,在高可用和强一致两个特性中,只能二选一:


  1. 要求MySQL主备间的数据强一致,不做主备自动切换。

  2. 借助MHA实现高可用,容忍MySQL主备间的数据不一致。


因此MySQL在容灾上无法同时满足数据强一致和服务高可用两个特性。


PhxSQL设计思路

可靠日志存储


实现一个以可靠日志存储为中心的架构来解决MySQL数据复制时产生的数据不一致问题。

Master将Binlog发送到BinlogSvr集群(可靠日志存储),Slave从BinlogSvr集群获取Binlog数据完成数据复制。 


Master在重启时,根据BinlogSvr集群的数据判断Pending Binlog是否已经被复制。如果未被复制则从Binlog File中删除。


利用BinlogSvr集群(可靠日志存储),使得Master(重启时检查本地Binlog是否和BinlogSvr集群的数据一致)和Slave(从BinlogSvr集群中获取Binlog)的数据保持一致,从而保证了整个集群中的MySQL主备间数据的一致性,如图8所示。 



图8  实现一个可靠日志存储保证各MySQL的数据一致

请求透传


在Master进行切换时,切换操作可能会导致部分MySQL Client仍然访问旧Master并读到旧数据。 


最直观的方法是修改MySQL Client API,在每一次进行查询时,先确认当前Master的位置。但此方法有以下缺点:


  1. 需要维护一个MySQL Client API的私有版本,维护成本高。

  2. 所有的调用端需要集成这个私有的MySQL Client API,操作成本很高。


为了避免修改MySQL Client API,可通过增加Proxy进行请求透传来解决上述问题。在每一个MySQL结点上增加一个Proxy,MySQL Client的请求不再直接访问MySQL而直接访问Proxy。Proxy根据Master的位置,将访问Slave机器的请求透传到Master机器,再进行MySQL操作。


通过增加Proxy进行请求透传,解决了MySQL Client分裂导致有可能读取到旧数据的问题,如图9所示。 



图9  实现一个可靠日志存储保证各MySQL的数据一致

自动选主


多机自动选主最常见的实现方式是由各个参与者发起投票,获得多数派支持的机器为Master,同时把Master信息记录到可靠存储。Master机器定期到可靠存储延长租约;非Master机器定期检查Master租约是否过期,从而决定是否要发起选举自己为Master的投票。


为了避免修改MySQL代码,在MySQL机器上增加一个Agent,由Agent来替代MySQL发起选主投票和续期租约;可靠存储继续由BinlogSvr承担。

Agent完成以下功能:


  1. Master机器的Agent监控本机MySQL是否正常服务;如果正常服务,则定期到可靠存储延长租约,否则停止续约。

  2. 非Master机器的Agent定期从可靠存储检查Master租约是否过期;如果过期,再检查本机MySQL是否已经执行了所有Binlog。如果已经执行了所有Binlog,则发起选举自己为Master的投票,如图10所示。

    图10 可靠日志存储和Agent共同实现自动选主机制

PhxSQL架构和实现

从上述思路可以得出PhxSQL的简单三层架构。对于每一个节点,部署3个模块(PhxSQLProxy,MySQL,PhxBinlogSvr)。多个节点上的PhxBinlogSvr组成一个可靠的日志存储集群和可靠的Master信息存储集群;PhxBinlogSvr同时承担Agent的责任。PhxSQLProxy负责请求的透传。Master结点上的PhxSync负责将MySQL的Binlog发送到PhxBinlogSvr,如图11所示。



图11  PhxSQL基本架构

Proxy(PhxSQLProxy)


  1. 请求透传


请求透传是Proxy主要的功能。主要解决在进行Master切换的时候,MySQL Client会被分裂,不同的Client可能连接到不同的MySQL。导致出现MySQL Client写入数据到错误的Master或者从错误的Master读取到错误的数据。


Proxy的请求透传分两种:


  1. 读写端口请求透传:Slave节点收到的请求透传给Master节点执行。Master节点收到的请求直接透传给本机MySQL执行。

  2. 只读端口请求透传:Master节点收到的请求透传给Slave节点执行。Slave节点收到的请求直接透传给本机MySQL执行,如图12所示。

    图12  Proxy请求透传流程


高性能:由于Proxy接管了MySQL Client的请求,为了使整个集群的读写性能接近单机MySQL,Proxy使用协程模型提高自身的处理能力。


Proxy的协程模型使用开源的Libco库。Libco库是微信团队开源的一个高性能协程库,具有以下特点:


  1. 用同步方式写代码,实现异步代码的性能。

  2. 支持千万级的并发连接。

  3. 项目地址https://github.com/tencent-wechat/libco


完全兼容MySQL:为了已有的应用程序能够不做任何修改就能迁移到PhxSQL,Proxy需兼容MySQL的所有功能。


兼容MySQL事务


MySQL事务管理基于连接,同一个事务的所有请求通过同一个连接通信。在事务处理中连接丢失,事务将被rollback(
http://dev.mysql.com/doc/refman/5.6/en/innodb-autocommit-commit-rollback.html)。


Proxy使用1:1连接模型完全兼容MySQL事务。每当MySQL Client发起一个连接到Proxy,Proxy都会相应地发起一个连接到MySQL。两条连接中,任意一个中断,另外一个也相应断开,对应的事务会被rollback,如图13所示。 


图13  Proxy的1对1事务连接模型

兼容MySQL权限 


MySQL的权限管理基于(用户,源IP)对,源IP是通过socket句柄反查获取。当请求通过Proxy连接到MySQL时,源IP为Proxy本地IP,权限管理会出现异常。


Proxy利用MySQL协议HEAD保留字段透传真实源IP到MySQ,MySQL再从HEAD保留字段获取正确的源IP进行权限管理,如图14所示。 



图14  Proxy通过修改MySQL协议兼容MySQL权限

PhxSync 


PhxSync的功能和MySQL的semisync插件类似。经过调研,对semisync插件的接口做少量的调整,就可以使用这些插件接口来实现PhxSync。


PhxSync功能主要是:


  1. 正常运行时提交Binlog:MySQL在正常写入或者更新数据时,会调用after_flush接口。PhxSync插件通过实现after_flush接口将MySQL新写入的Binlog提交到本机的BinlogSvr,由本机BinlogSvr通过Paxos协议同步到BinlogSvr集群。

  2. 重启时校准本地Binlog:MySQL在重启时通过查询BinlogSvr集群判断本地Pending Binlog的状态。如果Pending Binlog未复制到BinlogSvr集群则从本地删除,保持本地的Binlog数据和BinlogSvr集群的Binlog数据一致。


由于MySQL没有提供在重启时的插件接口,为了后续维护方便,在MySQL代码层抽象出了一个新插件接口before_binlog_init用于校准Binlog。


上述对after_flush接口的调整,和新增的before_binlog_init接口已经提交补丁给MySQL官方(http://bugs.mysql.com/bug.php?id=83158)。


PhxBinlogSvr


PhxBinlogSvr主要负责存储Binlog和Master信息的维护。在数据复制阶段,通过Paxos协议保证PhxBinlogSvr各节点的数据一致性(下文称PhxBinlogSvr为BinlogSvr)。


  1. PhxPaxos库:BinlogSvr使用PhxPaxos库进行数据的复制。PhxPaxos库是微信团队开源的Paxos类库,具有以下特性:1. 保证各节点的数据一致。

  2. 保证集群机器超过一半存活还能服务。

  3. 高性能。

  4. 功能完善。

  5. 稳定性经过大规模验证。

  6. 接口方便易用。

  7. 项目地址https://github.com/tencent-wechat/phxpaxos

BinlogSvr异常情况处理

防止Slave的节点提交数据


当旧Master在提交数据时由于网络问题数据包被卡在网络,且新Mater已经成功切换时,或者人为错误直接往Slave节点的MySQL写入数据时,则会出现Slave节点提交数据的情况。多节点同时提交数据会出现BinlogSvr的Binlog数据和MySQL存储的Binlog数据不一致的情况。


BinlogSvr存储了集群内的Master信息。当其收到MySQL提交的数据时,可根据Master信息拒绝非Master节点的提交,如图15所示。


图15  BinlogSvr通过Master信息拒绝非Master节点的提交

防止Master提交错误数据


在某些情况下,Master可能会重新发送数据或者发送错误数据。譬如在网络不好的情况下Master由于提交数据超时而重发数据。磁盘发生故障或者数据被错误回滚或者修改的时候,Master会提交错误的数据。


BinlogSvr使用乐观锁机制来防止Master的异常提交。在MySQL提交数据给BinlogSvr时,以本机MySQL已经执行的GTID为乐观锁,提交的内容为(本机MySQL已经执行的最新GTID,本次要提交的Binlog)。BinlogSvr通过检查请求中(本机MySQL已经执行的最新GTID)和自身保存的最新GTID是否匹配来拒绝重新发送或者异常发送的数据,如图16所示。 



图16  BinlogSvr使用乐观锁拒绝Master在数据异常的情况下提交数据

  1. 支持MySQL原生复制协议:为了让Slave能从BinlogSvr获取Binlog,最好的方式就是BinlogSvr支持MySQL原生的复制协议,这样不用对Slave做任何修改,如图17所示。

    图17  BinlogSvr支持MySQL使用原生复制协议获取Binlog数据

  2. Master管理:BinlogSvr除了存储MySQL的Binlog数据,还存储了Master信息。同时还承担了Agent的角色,负责监控MySQL的状态,必要时发起选举自己为Master的投票。


BinlogSvr通过Paxos协议进行Master选举,选举成功后成为Master并拥有租约。通过Paxos协议选举保证了最终只产生一个Master且每个节点记录了一致的Master信息。

PhxSQL效果

PhxSQL数据一致性


通过比较PhxSQL集群中各节点的数据(MySQL Binlog,PhxPaxos,BinlogSvr) 判断各节点数据是否一致,如图18所示。 


图18  PhxSQL 3机数据对比

Master自动切换


通过观察Master宕机时各节点的流量变化判断Master是否顺利切换。下图中的红线代表流量。当Master宕机时,流量会随之转移,代表Master顺利切换,如图19所示。 



图19  PhxSQL进行Master切换时各节点的写入流量变化

PhxSQL性能


  • MySQL版本:Percona 5.6.31-77.0

  • 机器信息:

    1. CPU :Intel Xeon CPU E5-2420 0 @ 1.90GHz * 24。

    2. Memory : 32G。

    3. Disk:SSD Raid10。

    4. Ping Costs:Master→Slave:3 ~ 4ms; client→Master :4ms。

  • 工具和参数:

    1. sysbench。

    2. –oltp-tables-count=10 –oltp-table-size=1000000 –num-threads=500。

    3. –max-requests=100000 –report-interval=1 –max-time=200。


PhxSQL的写性能比MySQL的半同步好,读性能由于多了一层Proxy导致比MySQL的半同步稍差。 



图20  PhxSQL和MySQL的性能对比

成功案例

QQ邮箱(域名邮箱)域名记录服务器:单个集群调用峰值40w/min。写请求平均耗时在20ms以下。读写比为20:1。机器配置:Intel Xeon CPU x3440 @ 2.53ghz 8 core,8GB ram。


订阅咨询:

  • 在线咨询(QQ):2251809102

  • 电话咨询:010-64351436

  • 更多消息,欢迎微博关注“@程序员编辑部”

点击下方“阅读原文”订阅程序员(含iOS、Android及印刷版)
博客地址:http://blog.yoqi.me/?p=2966
扫我捐助哦
喜欢 0

这篇文章还没有评论

发表评论