zoukankan      html  css  js  c++  java
  • 主备延迟以及备库并行复制策略

    保证mysql高可用

    • 主备延迟

      • 原因:运维主动操作,软件升级,主库机器掉电。
    • 同步延迟

      1. 主库A执行完成一个事务写入binlog,时刻T1;
      2. 传给备库,备库B接收到binlog时刻为T2;
      3. 备库B执行完成这个事物,时刻T3.
      • 同步延迟,即同一个事物,T3-T1之间的差值。show slave status可以显示当前备库延迟了多少秒。
    • 主备延迟的来源

      • 有些部署条件下,备库所在机器性能比主库所在性能差。在更新过程会触发大量的读操作。所以,当备库主机上的多个备库都在争抢资源的 时候,就可能会导致主备延迟了。
      • 备库的压力大,备库执行了很多分析语句,备库的查询占用了大量的CPU资源,造成了主备延迟。处理方法:一主多从,分担读的压力。
      • 大事物,如果一个主库上的语句执行10分钟,那这个事务很可能就会导致从库延迟10分钟。

    可靠性优先策略

    • 在双M结构下,两个数据库主备切换的过程如下:

      1. 判断备库B现在的seconds_behind_master,如果小于某个值(比如5秒)继续下一步,否 则持续重试这一步;

      2. 把主库A改成只读状态,即把readonly设置为true;

      3. 判断备库B的seconds_behind_master的值,直到这个值变成0为止;

      4. 把备库B改成可读写状态,也就是把readonly 设置为false;

      5. 把业务请求切到备库B

    • 可以看到,这个切换流程中是有不可用时间的。因为在步骤2之后,主库A和备库B都处于 readonly状态,也就是说这时系统处于不可写状态,直到步骤5完成后才能恢复。

      在这个不可用状态中,比较耗费时间的是步骤3,可能需要耗费好几秒的时间。这也是为什么需 要在步骤1先做判断,确保seconds_behind_master的值足够小。

    可用性优先策略

    • 如果强行把上述步骤4,5最开始执行,系统几乎没有不可用时间,但是可能会出现数据不一致

      • 假设一个表的初始化

        mysql> 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);
        
        insert into t(c) values(4);
        insert into t(c) values(5);
        

        假设,现在主库有大量更新,导致主备延迟达到5秒,在插入一条c=4的语句后,发生了主备切换。如图:采用可用性优先策略,且binlog_format=mixed时的切换流程和数据结果。

      1. 步骤2中,主库A执行完insert语句,插入了一行数据(4,4),之后开始进行主备切换。
      2. 步骤3中,由于主备之间有5秒的延迟,所以备库B还没来得及应用“插入c=4”这个中转日志,就开始接收客户端“插入 c=5”的命令。
      3. 步骤4中,备库B插入了一行数据(4,5),并且把这个binlog发给主库A。
      4. 步骤5中,备库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)这两行数据,都不会被对方执行。

    在可靠性优先的情况下,异常切换会是什么效果

    • 假设,主库A和备库B间的主备延迟是30分钟,这时候主库A掉电了,HA系统要切换B作为主库。 我们在主动切换的时候,可以等到主备延迟小于5秒的时候再启动切换,但这时候已经别无选择了。
    • 采用可靠性优先策略的话,你就必须得等到备库B的seconds_behind_master=0之后,才能切换。但现在的情况比刚刚更严重,并不是系统只读、不可写的问题了,而是系统处于完全不可用的状态。因为,主库A掉电后,我们的连接还没有切到备库B。
    • 但是保持B只读呢?这样也不行。这段时间内,中转日志还没有应用完成,如果直接发起主备切换,客户端查询看不到之前 执行完成的事务,会认为有“数据丢失”。 虽然随着中转日志的继续应用,这些数据会恢复回来,但是对于一些业务来说,查询到“暂时丢 失数据的状态”也是不能被接受的。
    • 在满足数据可靠性的前提下,MySQL高可用系统的可用性,是依赖于主备延迟的。延迟的时间越小,在主库故障的时候,服务恢复需要的时间就越短,可用性就越高。

    备库的并行复制

    • 一个箭头代表了客户端写入主库,另一个箭头代表备库sql_thread执行中转日志。箭头粗细来表示并行度的话,第一个箭头要粗于第二个箭头。

    • 日志在备库上的执行,就是图中备库上sql_thread更新数据(DATA)的逻辑。如果是用单线程的 话,就会导致备库应用日志不够快,造成主备延迟。

    • coordinator就是原来的sql_thread, 不过现在它不再直接更新数据了,只负责读取中转 日志和分发事务。真正更新日志的,变成了worker线程。而work线程的个数,就是由参数 slave_parallel_workers决定的。根据我的经验,把这个值设置为8~16之间最好(32核物理机的情况),毕竟备库还有可能要提供读查询,不能把CPU都吃光了。

      • 不能造成更新覆盖,要求更新同一行的两个事物,必须分发到一个worker中(因为无法决定那个worker先执行)
      • 同一个事物不能被拆开,必须放到同一个worker中(保持事物的原子性)

    并行策略一:按表分发策略

    • 按表分发事务的基本思路是,如果两个事务更新不同的表,它们就可以并行。因为数据是存储在 表里的,所以按表分发,可以保证两个worker不会更新同一行。当然,如果有跨表的事务,还是要把两张表放在一起考虑的。

    • 每个worker线程对应一个hash表,用于保存当前worker执行队列里的事物所涉及的表,key为库名.表名,value是一个数字,表示队列中有多少个事物修改这个表。

    • 在有事务分配给worker时,事务里面涉及的表会被加到对应的hash表中。worker执行完成后, 这个表会被从hash表中去掉。

    • 事物T的分配流程,和应当遵循的原则

      1. 假设在图中的情况下,coordinator从中转日志中读入一个新事务T,这个事务修改的行涉及到表 t1和t3。
      2. 由于事务T中涉及修改表t1,而worker_1队列中有事务在修改表t1,事务T和队列中的某个事 务要修改同一个表的数据,这种情况我们说事务T和worker_1是冲突的。
      3. 按照这个逻辑,顺序判断事务T和每个worker队列的冲突关系,会发现事务T跟worker_2也 冲突。
      4. 事务T跟多于一个worker冲突,coordinator线程就进入等待。
      5. 每个worker继续执行,同时修改hash_table。假设hash_table_2里面涉及到修改表t3的事务 先执行完成,就会从hash_table_2中把db1.t3这一项去掉。
      6. 这样coordinator会发现跟事务T冲突的worker只有worker_1了,因此就把它分配给 worker_1。
      7. coordinator继续读下一个中转日志,继续分配事务。
      1. 如果跟所有worker都不冲突,coordinator线程就会把这个事务分配给最空闲的worker;

      2. 如果跟多于一个worker冲突,coordinator线程就进入等待状态,直到和这个事务存在冲突 关系的worker只剩下1个;

      3. 如果只跟一个worker冲突,coordinator线程就会把这个事务分配给这个存在冲突关系的 worker。

    • 这个按表分发的方案,在多个表负载均匀的场景里应用效果很好。但是,如果碰到热点表,比如 所有的更新事务都会涉及到某一个表的时候,所有事务都会被分配到同一个worker中,就变成单 线程复制了。

    按行分发策略

    • 按行复制的核心思路是:如果两个事务没有更新相同的行,它们在备库上可以并行执行。显然,这个模式要求binlog格式必须是row。

    • 这时候,我们判断一个事务T和worker是否冲突,用的就规则就不是修改同一个表,而是修改同一行。

    • 按行复制和按表复制的数据结构差不多,也是为每个worker,分配一个hash表。只是要实现按 行分发,这时候的key,就必须是 库名+表名+唯一键的值,同时还要考虑唯一键,比如:

      • CREATE TABLE `t1` (
        `id` int(11) NOT NULL,
        `a` int(11) DEFAULT NULL,
        `b` int(11) DEFAULT NULL,
        PRIMARY KEY (`id`),
        UNIQUE KEY `a` (`a`)
        ) ENGINE=InnoDB;
        insert into t1 values(1,1,1),(2,2,2),(3,3,3),(4,4,4),(5,5,5);
        

      这是可能会报唯一键冲突,这是应该是 库名+表名+索引a的名字 +a的值

    • 比如,在上面这个例子中,我要在表t1上执行update t1 set a=1 where id=2语句,在binlog里面记录了整行的数据修改前各个字段的值,和修改后各个字段的值。

    1. key=hash_func(db1+t1+PRIMARY+2), value=2; 这里value=2是因为修改前后的行id值不 变,出现了两次。
    2. key=hash_func(db1+t1+a+2), value=1,表示会影响到这个表a=2的行。
    3. key=hash_func(db1+t1+a+1), value=1,表示会影响到这个表a=1的行。
    • 这两个方案其实都有一些约束条件:

      1. 要能够从binlog里面解析出表名、主键值和唯一索引的值。也就是说,主库的binlog格式必须是row;

      2. 表必须有主键;

      3. 不能有外键。表上如果有外键,级联更新的行不会记录在binlog中,这样冲突检测就不准确。

    • 在多行大事物的情况下,按行分发策略有两个问题:

      1. 耗费内存。比如一个语句要删除100万行数据,这时候hash表就要记录100万个项。
      2. 耗费CPU。解析binlog,然后计算hash值,对于大事务,这个成本还是很高的。
    • 所以,在实现这个策略的时候会设置一个阈值,单个事务如果超过设置的行数阈值(比如,如 果单个事务更新的行数超过10万行),就暂时退化为单线程模式,退化过程的逻辑大概是这样 的:

      1. coordinator暂时先hold住这个事务;

      2. 等待所有worker都执行完成,变成空队列;

      3. coordinator直接执行这个事务;

      4. 恢复并行模式。

  • 相关阅读:
    hdu 2485 Destroying the bus stations 迭代加深搜索
    hdu 2487 Ugly Windows 模拟
    hdu 2492 Ping pong 线段树
    hdu 1059 Dividing 多重背包
    hdu 3315 My Brute 费用流,费用最小且代价最小
    第四天 下载网络图片显示
    第三天 单元测试和数据库操作
    第二天 布局文件
    第一天 安卓简介
    Android 获取存储空间
  • 原文地址:https://www.cnblogs.com/jimmyhe/p/11137328.html
Copyright © 2011-2022 走看看