微信开源PhxSQL背后:强一致高可用分布式数据库的设计和实现哲学

rmba9494 8年前
   <p><img src="https://simg.open-open.com/show/eb196357d7e6144391b37034aa43e19a.jpg"></p>    <p>微信技术团队最近在开源开放上动作不断,后端方面前段时间他们开源了生产级paxos类库PhxPaxos,现在又开源了高可用分布式数据库PhxSQL,本文我们将了解一些PhxSQL背后的故事,以及它设计和实现的哲学。</p>    <p>老司机简介</p>    <p><strong>陈明</strong> , PhxSQL负责人。2012年加入微信,先后负责微信消息、朋友圈、账号、关系链、全球分布式部署、PhxSQL等项目,见证了微信后台基础架构支持从一亿用户到八亿用户、从单点到全球分布、从粗放到追赶和超越的历史过程。在加入微信之前,陈明在搜搜从事搜索架构建设和在微软亚洲研究院研究分布式架构及存储。陈明从清华大学计算机系获得博士学位,研究方向是分布式系统。</p>    <p>PhxSQL开源地址见:</p>    <p><a href="/misc/goto?guid=4959716578932726719" rel="nofollow,noindex">https://github.com/tencent-wechat/phxsql</a></p>    <h2><strong>为何研发PhxSQL?</strong></h2>    <p>PhxSQL是一个兼容MySQL、服务高可用、数据强一致的关系型数据库集群。PhxSQL以单Master多Slave方式部署,在集群内超过一半机器存活的情况下、即可提供服务,并且自身实现自动Master切换、保证数据一致性。PhxSQL不依赖于Zookeeper等任何第三方做存活检测及选主。PhxSQL基于MySQL的一个分支Percona 5.6开发,功能和实现与MySQL基本一致。</p>    <p>PhxSQL可以提供和zookeeper相同的强一致性与高可用性,并且支持serializable级别的事务隔离。</p>    <p>陈明表示,微信研发PhxSQL是为了解决在应用MySQL时一个普遍且巨大的痛点:强一致和高可用,MySQL不支持PhxSQL面向的分布式环境下强一致和高可用应用场景,而这在金融和账号类关键应用是必须的。据他们所知,目前还未有类似的公开方案,因此,只能自己研发。</p>    <p>后面的正文里面,也会详细的讨论MySQL在分布式环境下的痛点。</p>    <p>PhxSQL目前应用在微信后台的账号系统、企业微信、及QQ邮箱。其中一个关键服务的集群由5台服务器构成一个组,容忍两机同时故障,写高峰2000/秒,读高峰60000/秒。</p>    <h2><strong>性能与架构迁移</strong></h2>    <p>在他们的基准测试中,PhxSQL相对于MySQL的写性能有16-25%的提升,读性能持平。具体测试报告参见 github/phxsql 网址。实际上,PhxSQL的主要目标是强一致、高可用、和serializable事务隔离;性能只是一个关注点。另外,PhxSQL将很快支持性能更好的5.7版本MySQL。</p>    <p>PhxSQL设计的一个基本原则就是完全兼容MySQL,MySQL客户端不需要任何修改就可以直接访问PhxSQL,以便于将现有的。但是由于MySQL客户端不支持服务器故障时连接到新的可用节点,他们提供了多种可选方案。</p>    <ol>     <li>PhxSQLProxy可以将客户端请求转发给可用节点。</li>     <li>PhxSQL client lib支持自动切换服务器,原有应用程序只要在源码中创建连接时传入PhxSQL服务器IP列表、链接时将MySQL client lib换成PhxSQL client lib即可。</li>     <li>VIP等方案也可以和PhxSQL配合使用。</li>    </ol>    <p>有些人可能会注意到PhxPaxos和PhxSQL都带有前缀Phx,陈明说Phx代表不死的凤凰,是凤凰英文单词phoenix的缩写。它将用于微信后端的一系列开源项目,后续他们将放出更多的开源项目,敬请期待。</p>    <h2><strong>PhxSQL的设计和实现哲学</strong></h2>    <p>PhxSQL发布以来,受到很多关注。作为热爱技术的码农,我们感谢大家的关心和支持,欢迎一切基于技术出发点的讨论。“Show you the code”之后,我们在这里谈谈PhxSQL的设计和实现哲学,也同时回答大家提出的一些疑问。</p>    <h3><em>1、</em> <strong>PhxSQL是什么?</strong></h3>    <p>PhxSQL是一个通过Paxos保证强一致和高可用的的MySQL集群。 PhxSQL建立在Paxos的一致性和MySQL的binlog流水基础上。主要原理简单来说:</p>    <ol>     <li>Paxos选出主机</li>     <li>主机把本机MySQL设置成可写的MySQL主机,在MySQL写binlog流程中拦截binlog流水、发送到Paxos,形成全局的binlog流水</li>     <li>备机把本机的MySQL设置成只读的MySQL备机,MySQL备机从全局binlog中拉取流水,重放和执行,从而主备MySQL一致</li>     <li>针对常见的业务场景,PhxSQL提供两个服务端口:强一致读写端口(ReadWritePort)和只读端口(ReadonlyPort);对数据要求强一致的业务,通过ReadWritePort来读写;只要求能读取但不要求最新数据的读请求(比如一些定时对账业务),可以通过ReadonlyPort来读取</li>    </ol>    <p>只要有多于一半机器工作和互联,PhxSQL就可以正常工作。</p>    <p><img src="https://simg.open-open.com/show/870516f161e957351d2cd0be30481cae.jpg"></p>    <p>图 1:PhxSQL架构</p>    <h3><em>2、</em> <strong>“强一致”和“高可用”是什么级别?</strong></h3>    <p>很多MySQL集群方案都宣称强一致和高可用。PhxSQL这方面有什么不同?</p>    <p>大家熟知的Zookeeper提供强一致和高可用。一致性有很多级别,从强到弱分别是:Strict(严格一致性),Linearizable(线性一致性),Sequential(序列一致性),Causal(因果一致性),Eventual等。</p>    <p>严格一致性只是一个理论模型。根据相对论,由于信息传播的速度不可能高于光速,严格一致性在实际中几乎无法实现。线性一致性的理论定义很复杂,不太严谨直观地讲,就是任何一个客户端都可以读到别的客户端写入的最新内容。这也是大家通常理解的强一致。</p>    <p>Zookeeper的强一致指“线性一致性”。高可用是说只要多于一半机器工作和互联即可在保证线性一致性的质量下正常工作。PhxSQL的强一致是指“线性一致性”,高可用是指只要多于一半机器工作和互联即可在保证线性一致性质量下工作。即,</p>    <p>PhxSQL提供和Zookeeper相同的强一致性和高可用性!</p>    <p>PhxSQL提供和Zookeeper相同的强一致性和高可用性!</p>    <p>PhxSQL提供和Zookeeper相同的强一致性和高可用性!</p>    <p>重要事情说三遍:)。大家可以把PhxSQL当Zookeeper使用,例如用来选主!</p>    <p>在数据库事务隔离方面,PhxSQL支持最高级别的serializable。在性能方面,PhxSQL提供明显优于MySQL半同步的写性能和几乎相同的读性能[22]。</p>    <p>细心的读者可能注意到,和通常的“高可用、强一致”说法顺序相反,这个小节的标题中,“强一致”有意放在了“高可用”的前面。在这里需要澄清一个误区。当谈到高可用时,有时会有意无意忽略或者降低一致性。</p>    <p>严格意义上来讲,可用性和一致性必须一块讨论,满足一致性要求前提下的可用性才有意义。打个不严谨的比方,一致性就像汽车的“安全性”,可用性就是汽车的“可用性”。如果一辆车号称“可用性”很好,可以连续工作10年,但从来不提“安全性”,那么这辆车的品质是值得怀疑的。</p>    <h3><em>3、</em> <strong>为什么要做PhxSQL?</strong></h3>    <p>虽然有很多NoSQL、NewSQL系统、以及多主多写MySQL集群、支持分库分表的MySQL集群,MySQL传统主备同步方案仍然具备很多新系统难以企及的优点。MySQL主备在主机上支持完整SQL、全局事务、以repeatable read和serializable级别的事务隔离,在金融、帐号等关键业务中有巨大的价值。同时,现有复杂MySQL应用迁移到新的非兼容系统成本也很高。</p>    <p>但是MySQL传统主备方案也有其缺点。最明显的就是主机故障后的自动换主和新旧主数据一致性(以及衍生的换主后主备一致性、各个备机之间一致性,这里就不详加讨论了),即所谓的一致性和可用性。</p>    <p>为了解决这个问题,有传统流派:用Zookeeper、etcd、或者其它第三方来检测心跳、选主、和切换。改造MySQL client让其能感知新的主机。或者为了能让传统MySQL client不加修改就能感知新的主机,部署MySQL代理服务器,让其将连到自身的MySQL client请求透明转发给新的主机。</p>    <p>为了减少主备间数据的落后,从而降低旧主机故障、某台备机被提升成新主机时,新旧主机之间、新主机和其它备机之间的差异,很多方案在主备同步机制上做了很多有益的工作。</p>    <p>例如semi-sync等待多数派备机应答,通过优化线程和网络、主备多通道、备机并行执行binlog流水等尽量减少主备之间差异。如果主备间任何时刻都完全一致,那么任何时刻换主都是强一致的。这句话的另外一个意思是,如果无法保证主备间任何时刻完全一致,那么当有持续不断的更新时,任何时刻的换主都是无法保证强一致的。</p>    <p>传统流派另外一个分支就是将MySQL本地磁盘换成更高可靠性的SAN,当MySQL主机故障时,将SAN挂接到备机上提供服务。而当SAN故障时怎么办?当需要跨机房部署时怎么办?</p>    <p>意识到传统流派的局限性,新出了Galera和MySQL Group Replication等。除了宣称提供强一致性和高可用性外,还支持master-master多点写入等诱人新特性。</p>    <p>世界上目前已知的经过理论证明和实际检验的一致性算法屈指可数:两阶段提交two-phase commit (2PC)、Paxos、Raft、Zookeeper Atomic Broadcast (ZAB)、Viewstamped Replication等。2PC虽然可以保证一致性,但在主机故障时无法工作,存在可用性问题。</p>    <p>目前被工业界广泛认可和应用的是Paxos和Raft,2PC一般在前两者帮助选主下使用。这里的一个小建议就是:如果一个分布式系统宣称支持线性一致性级别的强一致和高可用,请先检查它使用的一致性算法。如果是新算法,请检查它的形式化证明或者逻辑证明。</p>    <p>因此,为支持线性一致性和高可用,同时完全兼容MySQL,我们在MySQL的基础上应用Paxos,设计和开发了PhxSQL。</p>    <h3><em>4、</em> <strong>PhxSQL的设计原则是什么?</strong></h3>    <p>从实际需求出发,除了前述强一致、高可用、完全兼容MySQL这3个明显、必须的设计原则,我们还提出以下3原则。</p>    <p>1、简单可逻辑证明的一致性模型</p>    <p>这可能是明显区别PhxSQL和其它方案的一个特点。一个经过逻辑证明的模型才是可靠的,一个建立在可靠模型基础上的系统也才是可信赖的。PhxSQL一致性模型建立在两个前提上:</p>    <ol>     <li>Paxos保证一致性。这个大家都可以接受。</li>     <li>各个MySQL的binlog流水一致,则各个MySQL机器之间数据“一致”。这个假设小有争议。例如即使binlog流水一致,由于不同的binlog格式、备机重放流水的不同配置,也会导致主备之间、不同备机之间的数据产生不同级别的“不同”,例如一条和当前时间相关的insert操作。这种不一致有些应用可以容忍,有些则不能。在这里,PhxSQL保证流水一致,而把格式和配置的自由留给应用和DBA根据具体场景确定。在最严格的配置下,binlog一致,则数据一致。</li>    </ol>    <p>在这两个前提下,PhxSQL的一致性模型通过Paxos,使得主机写入Paxos的binlog流水与备机从Paxos里拉取的binlog流水一致,从而保证MySQL数据的一致性。详细证明过程我们将另外提供。模型和证明过程都很简单,大家在读完源码后也可以尝试:)。</p>    <p>但即使模型正确,PhxSQL正确实现了这个模型吗?用通俗的话讲,没有bug。码农都知道这是个巨大的挑战。从一个算法和模型到正确的实现之间差距是巨大的。例如,正确实现Paxos挑战就很大。为了尽量减少bug,我们选择了简单和易理解同时应用广泛的Paxos作为一致性协议。</p>    <p>为了实现一个“生产”级别的Paxos,三位主要码农各自独立实现了Paxos,在各自测试完正确性后,三套Paxos之间作为一组Paxos的独立节点互操作检验正确性,最后再集体实现一个发行版本PhxPaxos!除了单元测试、系统测试外,测试环境还随机高频率独立重启机器、对网络包进行乱序、延迟、重复等以对系统进行充分测试。</p>    <p>有读者可能问,Paxos慢且网络延迟大,PhxSQL为什么不实现一个“优化”版?Paxos有各种版本,例如Fast Paxos、EPaxos。但这些都是Paxos,遵循Paxos的基本操作,所作的改变,都做了严格的形式化或者逻辑证明。</p>    <p>PhxPaxos严格遵照Paxos算法实现,没有做任何改变或者“优化”。PhxPaxos的正常写操作的网络延迟是一个网络RTT,已经是任何算法理论上能达到的最快速度。</p>    <p>现在,对于PhxSQL来说,切换主机是一件很平常和容易的操作,就像往MySQL里插入一条数据一样平常和容易。</p>    <p>2、最小侵入MySQL原则</p>    <p>MySQL是个巨大的快速演进的生态系统,保证PhxSQL中的MySQL与官方MySQL的兼容性与可升级性是必须的。这使得MySQL应用可以快速迁移到和运行在PhxSQL上。这也使得PhxSQL可以迅速响应官方MySQL的升级,将PhxSQL中的旧版本MySQL升级到新的官方MySQL,从而获得新的特性、性能、稳定性、和安全性提升。</p>    <p>这就要求PhxSQL中的MySQL对官方MySQL改动尽量少。实际上,PhxSQL版MySQL只更改了三个小地方:修改了binlog插件接口中一个函数的参数;在MySQL启动时新增了一个插件函数用于检查MySQL本地的binlog文件,在MySQL协议中透传了真正的客户端IP以兼容授权功能(如果应用不需要可以不修改)。</p>    <p>这几个改动是如此小,且涉及的是几乎稳定不变的流程,使得PhxSQL中MySQL跟随官方MySQL升级可以无缝完成。实际上,我们正和MySQL社区沟通,希望把这几处修改并入官方版本,从而使得PhxSQL以后可以完全使用官方版本MySQL,也使得更多人可以方便采用PhxSQL。</p>    <p><img src="https://simg.open-open.com/show/21242e7ea63db2ece9fd81cb9b805cd3.png"></p>    <p>(a)</p>    <p><img src="https://simg.open-open.com/show/d41359d3dadb2f6b79e640814dd1f67e.png"></p>    <p>(b)</p>    <p>图 2:PhxSQL对MySQL的关键修改。(a)是MySQL。(b)是PhxSQL中的MySQL。PhxSQL修改了after_flush这个函数的参数,新增了在MySQL启动时调用的before_recovery这个函数。</p>    <p>也正是因为这个原因,我们没有因为可以减少模块个数而把PhxSQLProxy、PhxBinlogSvr放到MySQL进程内。为了保证和应用最广泛的MySQL单机版兼容,也没有在事务层和存储层介入或修改。</p>    <p>3、简单的架构、部署、和运维</p>    <p>在满足要求的前提下,简单的架构有很多好处,例如开发、维护、诊断、维护、可靠性等等都变得容易。PhxSQL只有3+1个模块。PhxSQL中的MySQL是必须的。PhxBinlogSvr负责全局binlog存储和同步、选主、集群成员管理等关键功能。</p>    <p>在很多MySQL主备集群方案中,使用Zookeeper、etcd、心跳检测、Agent等承担选主的功能。PhxSQLProxy则作为传统MySQL client访问PhxSQL中MySQL服务的代理,使得PhxSQL的换主操作对于传统MySQL client透明。</p>    <p>在其它集群方案中,一般也是通过代理MySQL proxy、或者虚拟网关VIP,向传统MySQL client屏蔽集群的换主操作,提供透明访问。可选模块是PhxSQL client lib,它修改了MySQL client库中的连接初始化函数,允许传入一个集群的PhxSQLProxy列表,从而在一个PhxSQLProxy没有响应时、自动访问其它可用PhxSQLProxy,进一步提高可用性。开发人员可以直接链接PhxSQL client lib。</p>    <p><strong>PhxSQL的部署和运维都很简单。</strong> 在部署时,只要在目标机各自装好PhxSQL,在配置中指定集群的机器的IP列表,PhxSQL即可运行。换主这个操作已经从运维层面转移到PhxSQL正常的工作流程。</p>    <p>通常MySQL集群中换主后可能需要人肉检查数据一致性、人肉“闪回”(这可能违反一致性保证,导致“幻读”);无法”闪回“导致必须清除某台MySQL,重新拉取数据备份,和追流水等。这些耗时、繁重、易错的操作在PhxSQL中已经完全不需要。</p>    <p>至于热升级和热变更集群成员更简单。PhxSQL支持rolling-update,可以逐步升级每台机器。变更集群成员是指往集群添加机器、撤出机器、和替换机器(原子操作)。在保证强一致和高可用前提下,热变更(不停服变更)是非常困难的。</p>    <p>PhxSQL通过在Paxos中实现成员变更解决了这个难题。PhxSQL提供了一个变更操作命令。当在新机器安装和启动PhxSQL后(要求MySQL已经加载一份较新的数据备份),可以在现有集群中一键将新机器引入、剔除一台旧机器、或者同时做两者。这三种操作都是原子操作!新机器会自动追流水。想想看,相当于Zookeeper支持热变更成员,是不是很令人激动的特性?</p>    <p>PhxSQL由于在同步层使用Paxos,天然支持多数据中心、多机房部署,两地三中心这种部署更是不在话下。对于PhxSQL来说,多数据中心和多机房部署与机房内部署没有区别。PhxSQL的性能取决于多数派机器之间的网络延迟。</p>    <h3><em>5、</em> <strong>为什么要开源?</strong></h3>    <p>作为热爱技术的码农,我们相信开源的技术可以使得这个世界更美好。从我们日常开发使用的Emacs/Vim、GCC、GDB,间接为大众提供社交、电子商务、信息服务的Linux、Apache、MySQL、PHP,到大众每日使用来沟通和娱乐的Android等,开源是整个互联网的基石,为全世界提供许多关键不可或缺的基础服务。我们充分享受了开源带来的技术进步、经济发展、和社会前进,我们也希望开源的PhxSQL可以回馈社区,帮助更多有需要的人。</p>    <p>另外,我们希望通过开源更好地改进PhxSQL。我们欢迎技术性讨论和志愿者提交修改。我们承诺开源的PhxSQL会一直更新。除了一些和内部运维支撑系统进行集成的功能(PhxSQL把这些功能抽象成插件,我们针对内部运维支撑系统实现了这些插件),开源版和内部版本将保持一致。</p>    <h3><em>6、</em> <strong>PhxSQL的局限性</strong></h3>    <p>在一个不完美的世界里,完美是不存在的。我们很坦诚指出PhxSQL存在的两个局限:</p>    <p>1、MySQL主机在执行SQL DDL命令(例如建库和建表命令)时可能存在一致性风险。</p>    <p>由于MySQL的innodb引擎不支持DDL回滚,如果主机在innodb已经commit这条DDL命令,但是这条命令的binlog还没到达PhxSQL的拦截点前宕机,则这条DDL binlog会在全局binlog中缺失,从而备机也不会收到这条binlog。</p>    <p>而为了保证线性一致性、serializable级别事务隔离、及“最小侵入MySQL”原则,我们也不想修改MySQL源码,提前截获DDL命令。考虑到DDL命令频度较低,我们后续准备在PhxSQLProxy加入检查和后续审计告警。也欢迎大家提出更好方案。</p>    <p>2、在写入请求量很大的系统中,MySQL备机流水可能落后较多;如果这个时候主机死机,备机暂时无法提升成新主机,造成系统在一段时间内不可写。</p>    <p>为了保证线性一致性,对于要求读取最新数据的请求(通过ReadWritePort发起的读请求)也将失败;需要等至少一台备机追完流水,被提升为主机才能响应读取最新数据的请求。</p>    <p>对于不需要读取最新数据的请求(通过ReadonlyPort发起的请求),可以从任意备机执行,但不保证线性一致性。(注意:PhxSQL保证无论MySQL主机流水领先MySQL备机多少,MySQL主机binlog流水和全局binlog流水是一致的,不会导致数据丢失和破坏线性一致性。)</p>    <p>MySQL备机追流水落后是基于binlog复制这种模式的一个潜在问题。事实上,不仅MySQL主备,任何一个多副本系统,只要每个写操作不等待所有副本返回,都会出现类似的有些副本落后的问题;而那些等待所有副本返回的模式,在耗时和可用性方面又存在问题。</p>    <p>可喜的是MySQL 5.7版本实现了并行复制机制,显著地提高了备机追流水的性能。PhxSQL将很快支持MySQL 5.7,对于写入请求量很大的场景也可以很大程度上避免备机追流水落后的情况。</p>    <h3><em>7、</em> <strong>Why Not?</strong></h3>    <p>1、为什么不支持多写?</p>    <p>多写想想就很诱人。多写可以充分利用每台机器写时需要的资源。例如某些写操作可能非常耗费CPU,多写可以把写操作分散在各台机器上,充分利用各个机器的CPU资源,极大提高写入的性能。多写使得换主没有存在的必要,也就没有换主时可能存在的不可写时间窗问题。多写还使得客户端可以就近写入,减少跨数据中心写入带来的网络延迟。</p>    <p>多写有两种:大家熟知的分shard或者组,各shard或者组间并行写入,以Google Spanner[8]为典型代表;在shard或者组内并行,以Galera和MySQL Group Replication为代表。</p>    <p>1.组间多写</p>    <p>组间多写是把数据分成多个不相交的shard,每个组的机器负责一个shard 。当一个事务涉及的数据(读集合和或写集合)都在某个组时,这种事务称为本地事务。当一个事务涉及的数据分布在超过一个组时,这种事务称为分布式事务。</p>    <p>本地事务可以在本组独立执行,组之间不需要任何通信。为了减少事务冲突带来的性能降低,一般都是由组内leader执行本地事务,通过Paxos等一致性协议保证组内机器的数据一致[8]。各个组间并行执行本地事务,可以极大提高本地型事务的写性能。</p>    <p>组间多写最大的阻碍是分布式事务,而分布式事务是非常昂贵的。在SQL的模型中,为了实现read repeatable级别的事务隔离,事务管理器需要检查两个并发事务的写数据集是否冲突;为了达到serializable级别的事务隔离,事务管理器需要检查两个并发事务的读数据集和写事务集是否冲突。</p>    <p>这一般通过严格两阶段锁(strict two-phase locking,严格2PL)和/或者多版本并发控制MVCC实现。当这些数据集跨组时,就涉及到跨组的机器通信。</p>    <p>一个组同时遇到本地事务和分布式事务时,在本组需要根据事务的隔离级别,由事务管理器仲裁执行。</p>    <p>以Google Spanner为例,一个涉及两个机器组(Spanner中的组是指Paxos组)事务就需要在coordinator leader和non-coordinator-participant leader之间两次通信,前者组内还涉及一次Paxos写操作,后者组内再加两次Paxos写操作[8,Sec. 4.2.1 Read-Write Transactions]。</p>    <p>当跨机房部署时,机器之间的网络延迟使得通信代价更加高昂。Spanner为了减少这种昂贵的跨组事务,要求所有数据都必须有Primary key,并且其它数据尽量挂接在Primary key下面,使得事务尽量在一个组内、且由组内leader执行。</p>    <p>2.组内多主多写</p>    <p>组内多主多写时每个机器都有完整的数据,但这份数据分成不相交的逻辑集合,每个机器负责一个集合的写入。这台机器称为这个集合的主机,这个集合称为这个主机负责的数据,其它机器称为这个集合的备机。</p>    <p>客户端将写操作发到所涉及数据的主机,由主机通过atomic broadcast原子广播将更新请求发送给组内所有的机器,包括主机本身[9]。Galera和MySQL Group Replication都是采用这种方法。</p>    <p><img src="https://simg.open-open.com/show/ee9e9730b18f2ad8a41fb275cdbe4cb4.jpg"></p>    <p>图 3:组内多主多写架构[9]</p>    <p>原子广播具有3个特性:</p>    <ol>     <li>如果一台机器执行一条消息所带的更新命令,那么所有的其它机器都执行这条命令(delivered)。这里“执行”指的是原子广播层将消息交给上层,真实的执行时刻由上层决定。在数据库中,这个上层一般是并发事务管理器,它决定这些消息的真实执行顺序。</li>     <li>所有机器以相同的顺序执行命令</li>     <li>如果一台机器成功广播了一条消息,那么最终所有机器都将执行这条消息</li>    </ol>    <p>使用原子广播后,事务的生命周期从prepare->committed/aborted改变为prepare->committing->committed/aborted。</p>    <p><img src="https://simg.open-open.com/show/9029337180f1e1e86d72b5dd31471ba6.jpg"></p>    <p>图 4:事务生命周期状态转换</p>    <p>当一个事务只涉及到一个集合的数据时,称为本地事务,由这个集合的主机的本地事务管理器先使用本地严格2PL仲裁,然后将committing状态的事务通过原子广播发给其它备机。</p>    <p>当一个事务涉及到多于一个集合的数据时,称为复合事务(complex transaction)。</p>    <p>这个事务所涉及的某个数据集合的主机将committing事务状态,包括读集合(如果需要serializable级别隔离。这里的一个小优化是采用dummy row减少可能极其庞大的读集合)、写集合、以及committing状态,通过原子广播发给全组进行仲裁。Galera和MySQL Group Replication都只是校验写集合,因此不支持serializable级别事务隔离。</p>    <p>每台机器都可以通过本地事务状态和原子广播收到的消息,独立判定committing事务最终是提交还是终止。这种判定由原子广播的特性保证全局一致。</p>    <p><img src="https://simg.open-open.com/show/0de1f58142f49f0e00059b4a0991f3cd.jpg"></p>    <p>图 5:Deferred Update Replication和Certification-based Replication事务执行时序</p>    <p>强调一下:在机器通过原子广播进行数据同步时,事务的最终结果不能在广播前决定,而是在执行这条消息依赖的前置消息及这条消息后才能决定。这称为Deferred Update Replication推迟的更新复制或者Certification-based Replication。</p>    <p>这里有个重要特点需要注意:只有收到一条消息的所有前置消息后,这条消息和所有未执行的前置消息才能由事务管理器并发执行。因此,这里引入了一定的串行化。</p>    <p>原子广播的突出优点是在低延迟局域网有很高的吞吐率。</p>    <p>但同时原子广播有不小的按机器个数放大的网络延迟,在非低延迟网络会显著放大网络延迟。MySQL Group Replication使用的Corosync和Galera支持的Spread都是基于Totem这个成员管理和原子广播协议。</p>    <p>Totem是个为低延迟局域网设计的协议。在Totem中,所有机器组成一个环(ring)。无论一台机器是否需要广播,令牌(token)在机器之间都按照环顺序传递。只有拿到令牌的机器才可以进行广播,即发出提交事务请求。因此在下一台机器收到上一台机器令牌的网络延迟期间,整个系统处于等待状态。</p>    <p>为了保证Safe Order Delivery,消息(实际是regular token,不是regular message)需要在环中循环两圈,才能知道是否可以执行,因此,消息延迟是(4f+3)*RTT/2,其中(2f+1)是机器的数目、f表示容错的机器数。</p>    <p>在一个典型的两地三中心部署中,这导致一次事务写操作延迟极其高昂。例如一地两中心的网络延迟一般可以控制在2ms(单向),上海-深圳间网络延迟一般是15ms(单向),则一次事务写操作的网络延迟是64ms!</p>    <p>在两地三中心的配置中,PhxSQL的主和一个备一般分别在一地的两中心,另一个在异地。在通常情况下,master的Paxos一次写入只需一个accept,并且只等最快的备机返回。这时PhxSQL的写延迟只有4ms!</p>    <p>相比Paxos这类协议,原子广播还有一个缺陷。当任意一台机器宕机或者网络中断时,Totem此时会超时,在踢掉宕机的机器、重新确定组成员之前,整个集群的消息停止执行,即写操作暂停。</p>    <p>对于read-only事务,只有去数据集合的主机读取、或者昂贵地读取原子广播Quorum台机器、或者使用类似Spanner的TrueTime技术读取任一符合资格的机器,才能保证线性一致性。Galera节点间有延迟,并且只读事务在本地执行,不支持线性一致性。如果MySQL Group Replication支持线性一致性,请不吝告知。</p>    <p>了解基于原子广播的组内多主多写模式的原理和优缺点后,使用多写模式还需要根据业务仔细划分数据集,尽量减少公共数据的使用,同时处理好自增key的细节问题,以减少事务间的跨机冲突。</p>    <p>PhxSQL建立在开源的PhxPaxos基础上,感兴趣的读者可以用PhxPaxos方便实现原子广播插件,加载到MySQL中,从而支持多写。</p>    <p>如果不要read repeatable或者serializable级别隔离的事务,例如简单的key-value操作,同时通过lease机制保证线性一致性,是可以做到高效率多写的。但这就违反了PhxSQL完全兼容MySQL和最小侵入MySQL的原则。</p>    <p>2、为什么不支持分库分表?</p>    <p>分库分表也是个诱人的选择:可以平行无限扩展读写性能。分库分表就是分组,上个小节已经讨论了分布式事务的高昂成本。另外,为了保证完全兼容MySQL、支持全局事务和serializable级别事务隔离,不大改MySQL就支持sharding是非常困难的。</p>    <p>大改又违反了“最小侵入MySQL”原则,并且可能引入新的不兼容性。在应用不要求全局事务和serializable级别事务隔离情况下,感兴趣的读者可以把PhxSQL作为一容错的MySQL模块,在上层构建支持分库分表的系统。因为PhxSQL本身的容错性,这样做比在MySQL基础上直接构建要简单,无需关心每个sharding本身的出错。</p>    <p>如果以后有需求,PhxSQL团队也可能基于PhxSQL开发一个分库分表的新产品。当然,这个产品难以提供PhxSQL级别的兼容性。</p>    <p>3、为什么这么纠结于serializable级别事务隔离性,read repeatable级别很多时候已经够用了啊?</p>    <p>我们在设计原则中已经提到,为了完全兼容MySQL。我们认为一项好的技术是一项简单方便用户的技术,提供符合用户直觉预期、不用看太多注意事项的技术是我们的体贴。我们很诚恳,也是为了方便用户。我们不想说PhxSQL完全兼容MySQL,然后在不起眼的地方blabla列出好几页蝇头小字的例外。事实上,对于关键业务来说,serializable是必要的、read repeatable是不足的。read repeatable有个令人讨厌的write-skew异常[12]。</p>    <p>举个例子。小薇在一个银行有两张信用卡,分别是A和B。银行给这两张卡总的信用额度是2000,即A透支的额度和B透支的额度相加必须不大于2000:A+B<=2000。</p>    <p>两个账户的扣款函数用事务执行分别是:</p>    <p>A账户扣款函数:</p>    <p>sub_A(amount_a):</p>    <p>begin transaction</p>    <p>if (A+B+amount_a <= 2000)</p>    <p>{ A += amount_a }</p>    <p>Commit</p>    <p>B账户扣款函数:</p>    <p>sub_B(amount_b):</p>    <p>begin transaction</p>    <p>if (A+B+amount_b <= 2000)</p>    <p>{ B += amount_b }</p>    <p>commit</p>    <p>假定现在A==1000,B==500。如果小薇是个黑客,同时用A账户消费300和B账户消费300,即amount_a == 400,amount_b == 300。那么这个数据库会发生什么事情呢?</p>    <p>如果是read repeatable级别隔离,sub_a和sub_b都会同时成功!最后A和B账户的透支额分别是A=1000+400=1400,B=500+300=800,总的透支额A+B=1400+800=2200>2000,超过了银行授予的额度!如果不是信用卡的两笔小消费,而是两笔大额转账,那么银行怎么办?</p>    <p>如果是serializable级别隔离,则sub_a和sub_b只有一个成功。具体分析有兴趣的读者可以自己完成。</p>    <p>4、为什么不把显著提升MySQL性能作为一个主要目标?</p>    <p>事实上,PhxSQL已经显著提升了MySQL主备的写入性能。与semi-sync比,在测试环境中,PhxSQL的写入性能比semi-sync高15%到20%以上。读性能持平。这是在满足完全兼容MySQL和最小侵入MySQL原则下所能获得的结果。</p>    <p>鉴于PhxSQL对MySQL的改动是如此之小,对性能有高要求的读者,可以方便地把PhxSQL中的MySQL换成其它高性能版本,获得更高性能。</p>    <p>5、为什么编译时不支持C++11以下标准?</p>    <p>作为热爱新技术的码农,我们真的很喜欢C++11中期待已久、激动人心的新特性,例如极大增强的模板、lambda表达式、右值和move表达式、多线程内存模型等,这将C++带入了一个新的时代,大大提高了码农搬砖的速度、编码的正确性、和程序的性能。有了C++11,PhxSQL的开发效率提高了很多。</p>    <p>8、与Galera及MySQL Group replication的比较</p>    <p>参见7.1.2小节。</p>    <h2><strong>结论</strong></h2>    <p>PhxSQL是一个完全兼容MySQL,提供与Zookeeper相同强一致性和可用性的MySQL集群。</p>    <p> </p>    <p> </p>    <p>来自:http://www.yunweipai.com/archives/9682.html</p>    <p> </p>