mysql 高可用方案漫谈

引言:       AliCloudDB HA系统是为阿里云 mysql,sqlserver,pgsql 三大数据库提供实例高可用的一整套技术方案,确保系统能够在故障中快速恢复。同时HA 也满足日常运维管理中涉及到主备切换...

引言:

      AliCloudDB HA系统是为阿里云 mysql,sqlserver,pgsql 三大数据库提供实例高可用的一整套技术方案,确保系统能够在故障中快速恢复。同时HA 也满足日常运维管理中涉及到主备切换的需求。由于mysql 在业内使用最为广泛,所以本系列先介绍mysql实例高可用的相关技术。

基本架构:

      目前使用最为广泛的实例架构是采用一主一备 share-nothing 的结构。数据链路支持RLB + dbnode 与 RLB + proxy + dbnode 两种。为简便起见,本文以proxy 链路,引擎为innodb 的实例为例介绍。

attachments-2016-09-q3IpUCeC57dc3898f053


主备同步模式支持半同步,与异步两种方式,强同步在mysql5.7中将被支持。

      这里简要介绍下三种同步方式的区别:

      异步模式:主库收到commit 请求后,依次执行:写redo log prepare,写入binlog,写redo log commit,返回客户端成功。

      半同步模式:主库收到commit 后,依次执行 redo log prepare,写binlog/发往备库(两个步骤并行),等待备库回复收到ack,redo log commit,给用户回复成功。

      强同步模式:主库收到commit 后,依次执行 redo log prepare,写binlog/发往备库(并行),等待备库fsync到磁盘后,redo log commit ,给用户恢复成功。

      以上步骤中,写入redo log 步骤受参数innodb_flush_log_at_trx_commit 影响。 分为0,1,2三种设置,具体含义这里不再赘述。写入binlog 步骤受 sync_binlog 参数影响,具体数字含义,也不再赘述。在两个参数双1 条件下,数据最为安全可靠,性能略有下降。本文假设我们都是设置在双1条件下。

健康检测与切换类型:健康检测:

      HA 使用update 语句对mysql 实例分别通过VIP,物理IP进行检测,这样检测的优点是可以检测从VIP到用户实例主库的整条链路的可用性,同时配合备库的io线程的健康状况,避免HA到用户主库网络故障情况下,主库实例健康时的误判。

      HA 每次update 成功时,HA都将主库的时间戳记录到HA当中,同时更新主备库的心跳表,让备库同步主库写入的时间戳,当发生切换时,对比HA中与实例备库中保存的时间戳,可以得到备库大致的延迟。作为主备延迟的判断依据之一。

当实例发生连接超时,连接重置,更新超时,socket超时时都被称作故障切换。

普通切换

      当发生mysql实例版本升级,规格升级,刷新实例参数等情况时,需要对实例做主备切换。切换基本步骤如下:

  • 预检查主备延迟,如果延迟较大,那么直接退出切换,等待下次切换。
  • 如果延迟在一定范围内,那么进入切换流程,调用proxy api 切换VIP 指向备库。Proxy 此时会hold 住用户新的连接和请求,不会访问备库(桥接)。关于桥接的具体详情可以参考proxy 技术方案的文章。
  • kill 主库连接,设置read_only参数为ON。等待主库瞬间连接上来的事务结束。
  • 读取主库master binlog 的位点信息,等待备库同步到该位点,由于之前进行过预等待所以此步骤不会等待很长时间,根据实际运行中数据观察可以控制在数秒以内。
  • 备库打开read_only 为OFF
  • 调用proxy api 放开用户连接的请求。整个切换过程不会超过10S,用户会感觉到服务器卡住一小段时间后,恢复正常。

故障切换

      mysql实例发生故障的环节可能多种多样,这里仅以主备库采用异步复制模式,并且主库所在主机故障导致的切换为例来讲解基本的故障恢复流程,其他类型的故障后面会再综合介绍。

  • 当主库自身发生故障时,HA连接主机后,返回的错误类型是连接超时,或者收到主库返回的rst包,那么进入切换流程。
  • 检查备库延迟自身SQL线程延迟,如果有延迟,那么不切换,等待备库应用relaylog。
  • 当备库SQL线程没延迟后,尝试连接主库,获取主库master binlog,如果能够获取,那么就等待备库跟上主库到该位点。如果不能获取(一般都是如此),那么对比主备库时间戳,如果时间戳在一定范围内(主备可能存在数据不一致),那么根据用户指定的策略决定是否切换。

A, 如果用户指定可用性优先,那么HA会将用户连接切换至备库,数据可能丢失,已经通报给用户。

B, 如果用户指定可靠性优先,那么不会切换VIP,人工介入重启主库或主实例,或拷贝redo log,修复数据,确保主备一致后,提供服务。

      由于异步模式在有主备IO线程延迟情况下可能存在数据丢失,我们数据库内核组专门开发了double binlog 复制方式,用于专门判断在切换时是否存在数据丢失,如果存在,那么可靠性优先条件下不切换,可用性优先条件下可以切换。

      在用户指定了半同步模式下,也有可能导致主备数据不一致,但是不会引起数据丢失(数据丢失的定义是用户commit 后收到成功的回复,但是数据库内没有该数据。对于执行超时,或者执行异常,客户端认为是unknown状态,那么数据库可以选择丢弃该事务,或者应用该事务)。Semi-sync条件下,主备故障切换后,备库按照新主库备份集来重新搭建,确保主备数据一致。

其他故障切换:

attachments-2016-09-uhUdtjdu57dc38d768a5

   以上图中各处都有可能发生故障,那么对于不同的故障情况,都要分别逐一考虑,目前HA从整体上已经保证了以上各种故障条件下,可以在用户指定策略下按预期切换。考虑篇幅过长,每种情况的分析放在下次分享中,这里暂不作介绍。

HA 自身高可用与高可靠

      HA 通过多台机器采用多MASTER结构平衡负载,每台HA负责该region的一部分实例,并动态平衡,同时如果有一台HA发生故障,那么其他HA机器会争抢该dead HA所负责实例,然后再次平衡负载,实现方法既可以很简单,也可以很复杂, ha 这里由于考虑多单元部署的成本问题,使用metaDB中一个表作为负载均衡器,使用另外一张表作为HA心跳表,同时metaDB 使用一主多备方案,避免单点故障。整体上解决了HA 自身的高可用问题。

主库故障:故障分类

一般的,我们会发现mysql 不可用的原因有几下几类:
1,主机硬件损坏,导致主机hang死,或者操作系统crash。此时客户端连接主机上的mysql进程时的表现是连接超时。因为不会回复ack包。此种情况与网络中断不能回包的表现是一样的,所以对于外部是无法判定是主机down还是网络故障的。我们遇到过raid 卡损坏,磁盘故障,电源模块损坏,起火等等。
2,操作系统配置或者环境问题,比如ipfilter 某个参数配置过小或者BUG,设置出错等,导致drop 掉某些包,会导致实例部分连接没有响应,但是主机可能还可以登录。此时与1的外部表现也是相同的。
3,mysql 自身BUG,或者某些异常 sql 导致mysql 自身crash,此种情况是mysql 进程crash,主机是完好的,所以可以通过连接主机其他端口验证出是进程故障还是主机故障。
4,mysql 实例性能问题,比如用户执行一个大事务,刷pageCache或者写log,导致磁盘IOUTIL 打满,此时表现是连接超时,或者连接可以建立,但是执行update语句超时。此时主库log 一般是可以正常传送到被库的。
      针对以上故障,我们可能需要进行故障切换,故障切换最重要的两条原则,1)保证数据安全,保证数据可靠性。2)在一定时间内能快速响应,提高可用性。

可靠性保障:可靠性概述

可靠性保障最重要的就是要确保被库与主库数据保持一致,进一步就是说主库写入的binlog都要能够传递到备库。在异步模式下,是无法保证的,在半同步模式下,大部分情况可以保证,为什么说是大部分情况,因为有可能是备库刚刚down机,启动后,正在IO线程追主库log,此时复制还是异步模式,但追上后,才会自动转为半同步模式。如果此时主库再down机,那么也一定会有部分log还在主库上。如果说要保证一定传到备库,保证数据强一致,那么当备库down机时,主库就不应该继续提供服务,此种用法在其他主流数据库中也可以设置,但是很少使用,因为对可用性影响较大,因为如果一旦备库down机,或者主备间网络断开,那么主库可用性立即就受到影响,这种对于一般用户来说是不可接受的。进一步,有人引入了一主多备模式,当有一个备库返回已经收到最后一条日志后,就可以给用户返回commit成功,这样提高了主库的可用性,同时也提高了成本。
      言归正传,在一主一备的阿里云主流模式下,我们对于需要较高可靠性的用户,推荐使用半同步模式。集团内部的MHA系统提供了当主库故障时,从原主库同步log到备库,追上一段后,再提供服务的方案,此种方案能work的前提是原主库依然可以连接并且读取磁盘,并且会牺牲一段可用性时间,好处是可以补充一段binlog,让备库数据与主库一致。其实是可以作为一个比较好的补充手段。
      RDS这里对用户实例做了24小时不间断的被库延迟监控,所以对于数据延迟的实例会提前报警,避免当主机完全不可恢复时,数据丢失。
##如何应对
考虑这样的场景,主备库分别部署在A,B两个机房做容灾,HA的两个节点也分别部署在两个机房,当A,B机房间发生网络故障,但是A,B机房自身正常时,两边的HA 都分别看见对端的实例节点放生故障,会将自己机房的实例提升为主库,那么此时如果两个机房都有流量进来,那么就可能导致数据库“双写”,也就是会发生著名的“脑裂”问题。
      如果要解决“脑裂”问题,两个节点是不够的。那么我们引入了第三个节点,部署在另外一个机房,该节点无数据,只负责“脑裂”判定。这样构成了一个三个节点的三角模式(mongoDB,mssql 都是类似做法),三个点可以分别部署zookeeper 客户端并且选主,同一时刻,3个节点间只能有一个为leader。
attachments-2016-09-03zA7W7b57dc39351c9b
  3个节点中任意两个节点活着,那么实例可用。如果3个节点中两个或者3个节点挂掉,实例不可用。当A机房挂掉,或者实例在A机房的主机挂掉,那么leader 在B,C机房产生,此时由于B 机房可以连通leader 那么认为自己可以继续服务;C 机房挂掉,那么leader 在A,B中产生,A,B 都能连通leader ,那么仍然都可以继续服务。
网络故障场景
考虑网络故障情况,
A为主,B为备,C为裁判:
       1)当A机房与B机房网络不通,3个节点都正常,A到C,B到C都正常。
           leader 在A,那么A,C为多数派,A继续提供服务。
           leader 在B,那么A摘除自己,B,C之间重新选择leader ,将B提升为新主库。
           leader 在C,那么A, B服务不受影响,但是备库复制线程中断。
       2)当A机房与C机房网络不通,
           leader 在A,那么C 不能获取状态,摘除,B能与A leader 连接,继续work,A由于与B连通,投票多数,那么继续work ,不受影响。
           leader 在B,那么A,C节点都与leader B可以通信,那么整体都可以work, 无任何影响。
           leader 在C,那么A与C不通,那么A自己down掉自己,B与leader C通,所以B可以work , 将B中实例节点提升为主库。
        3)当A与B,C机房都不通,
                leader在A,B,C重新选leader ,那么A自己不work,然后B提升为主库。
                leader在B或者C, 不必选leader , A自己不work,然后B提升为主库。
        4)当B与C不通,与A通,B为备库,自己摘除自己,不影响可用。
                leader在A,B与leader A通,那么不受影响。
                leader在B,A和B构成多数派,继续提供服务。
                leader在C,那么由于A与C通,B摘除自己,主库A继续提供服务。
        5)当B与A,C都不通,A,C之间通,那么在A,C间重新选leader , 然后A继续提供服务。
        6)当C与A,B 都不通,那么A,B将重新选择leader ,A继续提供服务。
       当B 为主库时,分析与上面类似,综上,可以认为当主库本身节点与leader 节点不通,或者自己是leader节点,但是与另外两个节点都不通,自己成为少数派时,会发生failover,其余情况都可以正常工作。解决了“脑裂“问题,关键点就在于当节点自身发现是少数派时,自我管理,自己将自己杀掉。
       同时,HA 按照正常逻辑提升备库为新主库,保证可用性。
#结合现实
回到现实中,因为条件有限,有些时候没办法都部署三机房,所以在两机房时,如果是C节点与B部署在一边,A节点此时是主库,那么当C所在的机房整体down机,那么数据库主节点A由于与leader节点不通,将自己关闭自己的写入,并且此时由于B与C所在节点down机,B也无法提供服务,那么此时实例就无法提供服务。
如果A节点主库与 B.C所在的机房网络不通,但是B,C机房可以提供服务,那么主库会自动failover到B节点,此时B和C选出一个leader , 继续提供服务,没有问题。
复制模式概述:
   异步模式:主库收到commit 请求后,依次执行:写redo log prepare,写入binlog,写redo log commit,返回客户端成功。

        半同步模式:主库收到commit 后,依次执行 redo log prepare,写binlog/发往备库(两个步骤并行),等待备库回复收到ack,redo log commit,给用户回复成功。这个有一个等待ack的超时,当超过设置的时长,那么自动降级为异步模式;当网络或备库恢复后,备库IO线程开始追赶主库,当追上后,复制模式会自动升级为半同步模式。

         强同步模式:主库收到commit 后,依次执行 redo log prepare,写binlog/发往备库(并行),等待备库fsync到磁盘后,redo log commit ,给用户恢复成功。

保证可靠性:

   异步模式下,由于主库写入事务时,不会传递到备库(无论物理复制还是逻辑复制),那么当主库down机时,备库数据和主库是很可能不一致的,所以此时是无法保证可靠性的。对于我们阿里云用户使用的mysql库,在此模式下就没任何办法了么?其实不是,在此模式下,我们提供了以下的措施来最大限度的保证主备一致性:

   1)备库一定是在应用完所有传递到备库的日志后,才开放可写。这样就最大限度保证备库的数据线是一致的,不会导致双写的问题(一边应用日志,一边接受写入)。

   2)主库增加时间戳,通过复制关系同步到备库,当主库故障时,HA系统(数据库高可用系统)通过比对最后一次成功写入主库的时间戳及备库上从主库接受到日志后应用掉后的时间戳进行比较,当大于某个阀值时,不做链路切换动作。这样能够保证主备间数据不一致的时间长度在可控范围内。

   3)判断主库故障的条件更加苛刻,当不是主库所在主机是不可恢复的故障时,优先使用主库重启的方式来解决,确保数据线的一致性。

   半同步模式下,当网络与主备库都正常情况下,主备库的数据一定是最终一致的(备库需要应用relay日志),当主库故障时,要么事务在主库还没有应用,没有传递日志到备库;要么事务在主库应用了,日志也传递到了备库。所以最终是一致的。

   那么是不是半同步下,数据就不丢了呢?我们前面说过,当网络,和备库出现间歇性问题时,那么半同步会降级为异步,那么如果恰恰在此时,备库的IO线程正在追赶主库的binlog位点,但是还没有完全同步上,主库down了(也就是说此时出现延迟的情况下),主备数据就会出现不一致。

   针对这个问题,我们研发了double binlog复制模式,该模式是在半同步基础上更进一步,在主备库间除了半同步复制通路,同时还有一个异步复制通路,平时在主备库及网络都健康情况下,由半同步通路持续的保持复制关系,确保主备数据一致;但是当在备库或主备间网络出现问题,一旦备库或网络恢复后,备库不再使用单一的链路来复制binlog,会同时开启两条复制链路,一条继续从上次备库最后一次接到的位点来复制,一条从主库写入的最新一条事务日志来复制,那么落后的复制不必追赶到最新的主库binlog位置才升级为半同步,而是追赶到备库恢复后的位点即可。这时两条复制通路的binlog就可以完整接上,然后等待备库继续掉日志后,主备数据就完全同步了。

总结:

   所以通过这个特性,我们能做到在主备出现延迟后,依然能够保证主备的一致性。目前金融专有云已经开始使用该复制模式,对数据要求性高的朋友依然可以使用mysql一主一备的低成本方式来满足企业的日常需求。

  • 发表于 2016-09-17 02:24
  • 阅读 ( 55 )

你可能感兴趣的文章

相关问题

0 条评论

请先 登录 后评论
石天
石天

437 篇文章

作家榜 »

  1. shitian 662 文章
  2. 石天 437 文章
  3. 每天惠23 33 文章
  4. 小A 29 文章