作者:陈俊超,腾讯微信后台高级工程师,前期主要负责微信后台分布式架构设计及核心模块的开发,包括微信摇一摇,查看附近的人,朋友圈架构,群聊等。目前致力于关系型数据库PhxSQL的设计和开发。
本文为《程序员》原创文章,未经允许不得转载,更多精彩文章请订阅《程序员》
本文详细描述了PhxSQL的设计与实现。从MySQL的容灾缺陷开始讲起,接着阐述实现高可用强一致的思路,然后具体分析每个实现环节要注意的要点和解决方案,最后展示了PhxSQL在容灾和性能上的成果。
设计背景
互联网应用中账号和金融类关键系统要求和强调强一致性及高可用性。当面临机器损坏、网络分区、主备手工或者自动切换时,传统的MySQL主备难以保证强一致性和高可用性。PhxSQL将MySQL集群构建在一致性完善的Paxos协议基础上,保证了集群内MySQL机器之间数据的强一致性和整个集群的高可用性。
原生MySQL的容灾缺陷
【MySQL容灾方案】
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。)
该情况下MySQL容易出现Master-Slave之间数据不一致的情况,官方也描述了该问题。
【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所示。
上述case只对旧Master的数据造成影响,不会使得MySQL Client读取到错误数据。但当Master连续出现两次宕机后产生Master切换,两次宕机间隔较短使得Pending Binlog未能及时复制到Slave,且期间有查询请求时(Master宕机→Master重启→查询数据→Master宕机→Master切换),MySQL Client会产生如图5所示的幻读(两次读到的结果不一致)。
【MySQL Client分裂】
当Master出现故障且产生Master切换时,由于原生MySQL缺乏调用端的通知/重定向机制,使得不同的Client可能访问不同的Master,导致数据的错误写入和读取,如图6所示。
【MySQL缺乏自动选主机制】
由于半同步复制不需要等待所有Slave的ACK,因此当Master出现故障时,需要选有最新Binlog的Slave为新的Master;而MySQL并没有内置这个选主机制,如图7所示。
【MySQL的容灾缺陷总结】
MySQL在容灾方面存在的问题:
对于原生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所示。
【请求透传】
在Master进行切换时,切换操作可能会导致部分MySQL Client仍然访问旧Master并读到旧数据。
最直观的方法是修改MySQL Client API,在每一次进行查询时,先确认当前Master的位置。但此方法有以下缺点:
为了避免修改MySQL Client API,可通过增加Proxy进行请求透传来解决上述问题。在每一个MySQL结点上增加一个Proxy,MySQL Client的请求不再直接访问MySQL而直接访问Proxy。Proxy根据Master的位置,将访问Slave机器的请求透传到Master机器,再进行MySQL操作。
通过增加Proxy进行请求透传,解决了MySQL Client分裂导致有可能读取到旧数据的问题,如图9所示。
【自动选主】
多机自动选主最常见的实现方式是由各个参与者发起投票,获得多数派支持的机器为Master,同时把Master信息记录到可靠存储。Master机器定期到可靠存储延长租约;非Master机器定期检查Master租约是否过期,从而决定是否要发起选举自己为Master的投票。
为了避免修改MySQL代码,在MySQL机器上增加一个Agent,由Agent来替代MySQL发起选主投票和续期租约;可靠存储继续由BinlogSvr承担。
Agent完成以下功能:
PhxSQL架构和实现
从上述思路可以得出PhxSQL的简单三层架构。对于每一个节点,部署3个模块(PhxSQLProxy,MySQL,PhxBinlogSvr)。多个节点上的PhxBinlogSvr组成一个可靠的日志存储集群和可靠的Master信息存储集群;PhxBinlogSvr同时承担Agent的责任。PhxSQLProxy负责请求的透传。Master结点上的PhxSync负责将MySQL的Binlog发送到PhxBinlogSvr,如图11所示。
【Proxy(PhxSQLProxy)】
请求透传是Proxy主要的功能。主要解决在进行Master切换的时候,MySQL Client会被分裂,不同的Client可能连接到不同的MySQL。导致出现MySQL Client写入数据到错误的Master或者从错误的Master读取到错误的数据。
Proxy的请求透传分两种:
高性能:由于Proxy接管了MySQL Client的请求,为了使整个集群的读写性能接近单机MySQL,Proxy使用协程模型提高自身的处理能力。
Proxy的协程模型使用开源的Libco库。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所示。
兼容MySQL权限
MySQL的权限管理基于(用户,源IP)对,源IP是通过socket句柄反查获取。当请求通过Proxy连接到MySQL时,源IP为Proxy本地IP,权限管理会出现异常。
Proxy利用MySQL协议HEAD保留字段透传真实源IP到MySQ,MySQL再从HEAD保留字段获取正确的源IP进行权限管理,如图14所示。
PhxSync
PhxSync的功能和MySQL的semisync插件类似。经过调研,对semisync插件的接口做少量的调整,就可以使用这些插件接口来实现PhxSync。
PhxSync功能主要是:
由于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)。
BinlogSvr异常情况处理
防止Slave的节点提交数据
当旧Master在提交数据时由于网络问题数据包被卡在网络,且新Mater已经成功切换时,或者人为错误直接往Slave节点的MySQL写入数据时,则会出现Slave节点提交数据的情况。多节点同时提交数据会出现BinlogSvr的Binlog数据和MySQL存储的Binlog数据不一致的情况。
BinlogSvr存储了集群内的Master信息。当其收到MySQL提交的数据时,可根据Master信息拒绝非Master节点的提交,如图15所示。
防止Master提交错误数据
在某些情况下,Master可能会重新发送数据或者发送错误数据。譬如在网络不好的情况下Master由于提交数据超时而重发数据。磁盘发生故障或者数据被错误回滚或者修改的时候,Master会提交错误的数据。
BinlogSvr使用乐观锁机制来防止Master的异常提交。在MySQL提交数据给BinlogSvr时,以本机MySQL已经执行的GTID为乐观锁,提交的内容为(本机MySQL已经执行的最新GTID,本次要提交的Binlog)。BinlogSvr通过检查请求中(本机MySQL已经执行的最新GTID)和自身保存的最新GTID是否匹配来拒绝重新发送或者异常发送的数据,如图16所示。
支持MySQL原生复制协议:为了让Slave能从BinlogSvr获取Binlog,最好的方式就是BinlogSvr支持MySQL原生的复制协议,这样不用对Slave做任何修改,如图17所示。
Master管理:BinlogSvr除了存储MySQL的Binlog数据,还存储了Master信息。同时还承担了Agent的角色,负责监控MySQL的状态,必要时发起选举自己为Master的投票。
BinlogSvr通过Paxos协议进行Master选举,选举成功后成为Master并拥有租约。通过Paxos协议选举保证了最终只产生一个Master且每个节点记录了一致的Master信息。
PhxSQL效果
【PhxSQL数据一致性】
通过比较PhxSQL集群中各节点的数据(MySQL Binlog,PhxPaxos,BinlogSvr) 判断各节点数据是否一致,如图18所示。
【Master自动切换】
通过观察Master宕机时各节点的流量变化判断Master是否顺利切换。下图中的红线代表流量。当Master宕机时,流量会随之转移,代表Master顺利切换,如图19所示。
【PhxSQL性能】
机器信息:
工具和参数:
PhxSQL的写性能比MySQL的半同步好,读性能由于多了一层Proxy导致比MySQL的半同步稍差。
成功案例
QQ邮箱(域名邮箱)域名记录服务器:单个集群调用峰值40w/min。写请求平均耗时在20ms以下。读写比为20:1。机器配置:Intel Xeon CPU x3440 @ 2.53ghz 8 core,8GB ram。
订阅2017年程序员(含iOS、Android及印刷版)请访问 http://dingyue.programmer.com.cn
由CSDN主办的中国云计算技术大会(CCTC 2017)将于5月18-19日在北京召开,Spark、Container、区块链、大数据四大主题峰会震撼袭来,包括Mesosphere CTO Tobi Knaup,Rancher labs 创始人梁胜、Databricks 工程师 Spark commiter 范文臣等近60位技术大牛齐聚京城,为云计算、大数据以及人工智能领域开发者带来一场技术的盛大Party。现在报名,只需399元就可以聆听近60场的顶级技术专家分享,还等什么,登陆官网(http://cctc.csdn.net/),赶快报名吧!