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

标签: dev | 发表时间:2017-04-15 00:00 | 作者:
出处:http://itindex.net/admin/pagedetail

作者丨陈俊超

责编丨卢凯



“本文详细描述了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及印刷版)

相关 [微信 数据库 phxsql] 推荐:

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

- - IT瘾-dev
“本文详细描述了PhxSQL的设计与实现. 从MySQL的容灾缺陷开始讲起,接着阐述实现高可用强一致的思路,然后具体分析每个实现环节要注意的要点和解决方案,最后展示了PhxSQL在容灾和性能上的成果. 互联网应用中账号和金融类关键系统要求和强调强一致性及高可用性. 当面临机器损坏、网络分区、主备手工或者自动切换时,传统的MySQL主备难以保证强一致性和高可用性.

微信开源 PhxSQL:高可用强一致的 MySQL 集群

- - 开源中国社区最新新闻
昨日,微信后台团队宣布其开源了 PhxSQL 项目,并将项目托管到 Github 上. PhxSQL 是一个兼容 MySQL、服务高可用、数据强一致的关系型数据库集群. PhxSQL 以单 Master 多 Slave 方式部署,在集群内超过一半机器存活的情况下,可自身实现自动 Master 切换,且保证数据一致性.

数据库sharding

- - 数据库 - ITeye博客
当团队决定自行实现sharding的时候,DAO层可能是嵌入sharding逻辑的首选位置,因为在这个层面上,每一个DAO的方法都明确地知道需要访问的数据表以及查询参数,借助这些信息可以直接定位到目标shard上,而不必像框架那样需要对SQL进行解析然后再依据配置的规则进行路由. 另一个优势是不会受ORM框架的制约.

数据库索引

- - CSDN博客推荐文章
索引是由用户创建的、能够被修改和删除的、实际存储于数据库中的物理存在;创建索引的目的是使用户能够从整体内容直接查找到某个特定部分的内容. 一般来说,索引能够提高查询,但是会增加额外的空间消耗,并且降低删除、插入和修改速度. 1.聚集索引:表数据按照索引的顺序来存储的. 2.非聚集索引:表数据存储顺序与索引顺序无关.

数据库事务

- - CSDN博客推荐文章
事务具备4个特性:原子性(Atomicity),一致性(Consistency),隔离性(Isolation)和持续性(Durability). 这4个特性也简称为ACID性. (1)原子性:事务是能够用中最小执行单位,具有不可再分性. (2)一致性:事务执行的结果,必须使数据库从一个一致性状态,变到另一个一致性状态.

数据库优化

- - 数据库 - ITeye博客
程序运行效率,优化应用程序,在SP编写过程中应该注意以下几点: . a) SQL的使用规范: .   i.尽量避免大事务操作,慎用holdlock子句,提高系统并发能力.   ii.尽量避免反复访问同一张或几张表,尤其是数据量较大的表,可以考虑先根据条件提取数据到临时表中,然后再做连接.   iii.尽量避免使用游标,因为游标的效率较差,如果游标操作的数据超过1万行,那么就应该改写;如果使用了游标,就要尽量避免在游标循环中再进行表连接的操作.

数据库事务

- - 数据库 - ITeye博客
事务传播发生在类似以下情形:. 假设methodB的配置是:. 如果methodA在事务里,那么methodB也在这个事务中运行. 如果methodA不在事务里,那么methodB重新建立一个事务运行. 如果methodA在事务里,那么methodB也在这个事务中运行. 如果methodA不在是事务里,那么methodB在非事务中运行.

数据库调优

- - 数据库 - ITeye博客
1、1、调整数据结构的设计. 这一部分在开发信息系统之前完成,程序员需要考虑是否使用ORACLE数据库的分区功能,对于经常访问的数据库表是否需要建立索引等. 这一部分也是在开发信息系统之前完成,程序员在这一步需要考虑应用程序使用什么样的体系结构,是使用传统的Client/Server两层体系结构,还是使用Browser/Web/Database的三层体系结构.

MySQL数据库的修复

- Xin - 博客园-首页原创精华区
找到mysql的安装目录的bin/myisamchk工具,在命令行中输入:. 然后myisamchk 工具会帮助你恢复数据表的索引. 好象也不用重新启动mysql,问题就解决了. 当你试图修复一个被破坏的表的问题时,有三种修复类型. 如果你得到一个错误信息指出一个临时文件不能建立,删除信息所指出的文件并再试一次--这通常是上一次修复操作遗留下来的.

Oracle 发布 NoSQL 数据库

- 冷月 - 博客园新闻频道
  Oracle 作为全球最大的关系型数据库提供商,在其产品链条中,也加入了 NoSQL 数据库这一环,而且这个新的数据库名字很霸气,就叫 NoSQL Database,想起了当年新浪微博更换 weibo.com 域名之时的一个笑话:. 原来有三家人做面包,张三家的面包叫三张牌面包,李四家的牌子叫李四牌面包,王五家出品的是王五牌面包,而突然有一天,张三家的面包改名了,叫面包牌面包.