当前位置 博文首页 > JavaEdge全是干货的技术号:【阿里数据库面试题解】MySQL高可用

    JavaEdge全是干货的技术号:【阿里数据库面试题解】MySQL高可用

    作者:[db:作者] 时间:2021-07-21 09:41

    在一个主备关系中,每个备库接收主库的binlog并执行。

    正常情况下,只要主库执行更新生成的所有binlog,都可以传到备库并被正确执行,备库就能达到跟主库一致的状态,这就是最终一致性

    但MySQL要提供高可用能力,只有最终一致性还不够。为什么呢?

    • MySQL主备切换流程–双M结构

    主备延迟

    主备切换可能是:

    • 主动运维动作
      比如软件升级、主库所在机器按计划下线等
    • 被动操作
      比如主库所在机器掉电。

    同步延迟

    与数据同步有关的时间点主要包括以下三个:

    1. 主库A执行完成一个事务,写入binlog,该时刻记为t1
    2. 之后传给备库B,备库B接收完该binlog的时刻记为t2
    3. 备库B执行完成该事务,该时刻记为t3

    主备延迟,就是同一事务,在 备库执行完成的时间主库执行完成的时间 之间的差值,即t3-t1。

    可以在备库执行show slave status,它的返回结果会显示SBM(简称 SBM),表示当前备库延迟了多少s。
    SBM 计算方法:

    1. 每个事务的binlog都有一个时间字段,以记录主库上写入的时间
    2. 备库取出当前正在执行的事务的时间字段的值,计算它与当前系统时间的差值,得到SBM

    其实SBM就是t3-t1。所以,可以用SBM作为主备延迟的值,这个值的时间精度是s。

    • 若主备库机器的系统时间设置不一致,不会导致主备延迟的值不准吗?
      不会的。因为,备库连接到主库时,会通过执行SELECT UNIX_TIMESTAMP()函数获得当前主库系统时间。若此时发现主库系统时间与自己不一致,备库在执行SBM计算时,会自动扣掉该差值。

    在网络正常时,日志从主库传给备库所需时间很短,即t2-t1非常小。即网络正常情况下,主备延迟的主要来源是备库接收完binlog和执行完该事务之间的时间差。

    所以主备延迟最直接的表现是,备库消费中转日志(relay log)的速度,比主库生产binlog的速度要慢。这可能是由哪些原因导致的呢?

    主备延迟的来源

    备库所在机器的性能 < 主库所在的机器性能
    

    部署的人会想,反正备库没有请求,所以可以用差点儿的机器。或把20个主库放在4台机器,而把备库集中在一台机器。

    但更新请求对IOPS的压力,在主库和备库上是无差别的。所以,做这种部署时,一般都会将备库设置为“非双1”模式。

    但实际上,更新过程中也会触发大量读操作。所以,当备库主机上的多个备库都在争抢资源时,就可能导致主备延迟。

    这种部署现在少了。因为主备可能发生切换,备库随时可能变成主库,所以主备库必须选用相同规格机器,并且做对称部署。

    我们也做了对称部署,但还有延迟,为啥?

    很可能备库的压力大。主库既然提供了写能力,那么备库可以提供一些读能力。或一些运营后台需要的分析语句,不能影响正常业务,所以只能在备库上跑。

    由于主库直接影响业务,大家使用起来会比较克制,反而忽视了备库的压力控制。结果备库上的查询耗费大量CPU,影响同步速度 =》主备延迟。

    这时一般可以这么处理:

    • 一主多从
      除了备库外,可以多接几个从库,让这些从库来分担读压力。大多采用该方案,因为数据库系统必须保证有定期全量备份能力。而从库,很适合用来做备份。
    • 通过binlog输出到外部系统
      比如Hadoop,让外部系统提供统计类查询的能力。

    从库和备库在概念上其实差不多。一般把会在HA过程中被选成新主库的,称为备库,其他的称为从库。

    我们也采用了一主多从,保证备库压力不会超过主库,但还主备延迟,为啥?

    可能就是大事务了。因为在主库,必须等事务执行完成才会写binlog,再传给备库。所以,若一个主库的语句执行10min,则该事务可能就会导致从库延迟10min。

    delete一次性删除太多数据

    比如,一些归档类数据,平时没有注意删除历史数据,等空间快满,SE要一次性删大量历史数据。又要避免在高峰期,所以会在晚上执行这些大量数据删除。

    结果,DBA半夜收到延迟报警。然后,DBA要求你后续再删数据时,要控制每个事务删除的数据量,分成多次删除。

    大表DDL

    计划内的DDL,建议使用gh-ost方案

    我们主库也没大事务,怎么还主备延迟?

    可能因为备库的并行复制能力。

    其他情况

    TODO。

    由于主备延迟的存在,所以在主备切换时,就有不同

    策略

    可靠性优先策略

    比如一开始的双M架构,切换过程如下:

    1. 判断备库B现在的SBM,若小于某值(比如5s)继续下一步,否则持续重试该步
    2. 把主库A改成只读状态,即把readonly设置为true
    3. 判断备库B的SBM值,直到该值=0
    4. 把备库B改成可读写状态:把readonly 设置为false
    5. 把业务请求切到备库B

    切换一般由HA系统完成。

    MySQL可靠性优先主备切换流程

    该切换流程中有不可用时间。因为在step2后,A、B都readonly,此时系统不可写,直到step5完成后才恢复。

    在这个不可用过程,较耗时的是step3,可能耗费几s。这也是为什么要在step1先做判断,确保SBM足够小。

    倘若一开始主备延迟就长如30min,而不先做判断直接切换,系统的不可用时间就会长达30min,一般业务都是不能接受的。

    系统的不可用时间,是由该数据可靠性优先的策略决定的。也可选择可用性优先的策略,来把这个不可用时间几乎降为0。

    可用性优先策略

    如果我强行把步骤4、5调整到最开始执行,也就是说不等主备数据同步,直接把连接切到备库B,并且让备库B可以读写,那么系统几乎就没有不可用时间了。

    我们把这个切换流程,暂时称作可用性优先流程。这个切换流程的代价,就是可能出现数据不一致的情况。

    接下来,我就和你分享一个可用性优先流程产生数据不一致的例子。假设有一个表 t:

    CREATE TABLE `t` (
      `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
      `c` int(11) unsigned DEFAULT NULL,
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB;
    
    insert into t(c) values(1),(2),(3);
    

    初始化数据后,主库和备库上都是3行数据。接下来,业务人员要继续在表t上执行两条插入语句的命令,依次是:

    insert into t(c) values(4);
    insert into t(c) values(5);
    

    假设,现在主库上其他的数据表有大量更新,导致主备延迟达到5s。在插入一条c=4的语句后,发起了主备切换。

    • 可用性优先策略,且binlog_format=mixed时的切换流程和数据结果。

    • step2:主库A执行完insert,插入了一行数据(4,4),之后开始进行主备切换

    • step3:由于主备之间5s延迟,所以备库B还没来得及应用“插入c=4”这个中转日志,就开始接收客户端“插入 c=5”的命令

    • step4:备库B插入数据(4,5),并把该binlog发给主库A

    • step5:备库B执行“插入c=4”这个中转日志,插入一行数据(5,4)。而直接在备库B执行的“插入c=5”这个语句,传到主库A,就插入一行新数据(5,5)。

    最终,A、B上出现两行不一致数据,是由可用性优先流程导致。

    若我还是想用可用性优先策略,但设置binlog_format=row,会咋样?

    row格式在记录binlog时,会记录新插入的行的所有字段值,所以最后只会有一行不一致。而且两边主备同步的应用线程会报错duplicate key error并停止。即这种情况,B的(5,4)和A的(5,5)这两行数据,都不会被对方执行:

    • 可用性优先策略,且binlog_format=row

    所以使用row格式,数据不一致更容易被发现。而使用mixed、statement,数据很可能悄悄地就不一致。若你过很久才发现数据不一致,那可能只能删库跑路了。

    主备切换的可用性优先策略会导致数据不一致。所以更推荐使用可靠性优先策略。毕竟对数据服务,数据的可靠性 > 可用性

    有没有哪种情况数据的可用性优先级就是更高呢?
    有个库的作用是记录操作日志。这时,若数据不一致,可通过binlog修复,而这短暂不一致也不会引发业务问题。
    同时,业务系统依赖于这个日志的写入逻辑,若该库不可写,会导致线上业务操作无法执行。
    这时候,你可能需要先强行切换,事后再补数据。
    事后复盘,想到个改进措施:让业务逻辑不要依赖于这类日志的写入。即日志写入这个逻辑模块应该可降级,比如写到本地文件或另外一个临时库。

    这种场景就可以使用可靠性优先策略了。

    按可靠性优先,异常切换会是什么效果?
    假设,主库A和备库B间的主备延迟是30min,这时主库A掉电,HA系统要切换B作为主库。在主动切换时,可以等到主备延迟小于5s时,再启动切换,但这时已经别无选择了。

    • 可靠性优先策略,主库不可用

    采用可靠性优先策略,必须得等到备库B的SBM=0后,才能切换。但现在比刚刚更严重,并不是系统只读、不可写,而是系统处于完全不可用。因为,主库A掉电后,我们的连接还没有切到备库B。

    • 能否直接切换到备库B,但保持B只读?
      不行。因为,这段时间内,中转日志还没有应用完成,若直接发起主备切换,客户端查询看不到之前执行完成的事务,会认为有“数据丢失”。

    虽然随着中转日志的继续应用,这些数据会恢复回来,但对于一些业务,查询到“暂时丢失数据的状态”不能被接受。

    在满足数据可靠性的前提下,MySQL高可用系统的可用性,依赖于主备延迟。延迟越小,在主库故障时,服务恢复需要时间越短,可用性越高。

    cs