虽然主从复制可以保证其最终一致性,利用了MySQL强大的binlog功能

但是,只有一个最终一致性是不够的,因为读还是在从库上读

图片

最好保证其强一致性

我们先说导致主备延迟的主要问题

1.同步延迟

主库A执行完成一个事务,写入binlog,这是T1时刻

然后传给了备库B,从库B在T2时刻接受

备库B执行完成后的时刻是T3

从T3到T1,这就是主备延迟,可以通过在备库上show slave status命令,来返回备库延迟了多少秒

seconds_behind_master,表示延迟了多少秒

这个值具体计算为,每个事务的binlog都有一个时间字段,记录主库上写入的时间

备库上取出正在执行的事务的时间字段的值,计算当前时间的差值,得到seconds_behind_master

备库执行完成的时间戳 – 主库执行完成的时间戳

一般来说,主库传给备库的时间很短的,主要消耗在了中转日志的消费速度上,relay log消费的速度比不上主库生产binlog的速度

主要包含

(1).备库性能差

虽然从库从库不会接收到写请求,可以使用差一点的机器,但是无论什么操作,对于IOPS的压力都是存在的

所以可以考虑设置为非双1模式

所以,在双M模式下,我们会采用多个相同配置IDE极其来进行部署

(2).备库的压力比主库的大

备库上会跑一些压力大的语句,可能会导致备库压力更大,索引,一般来采用中间件来让从库分摊读的压力

(3)大事务

因为主库会在执行完成一个大事务之后,然后将这个大事务整个的传给了备库,于是备库会有一定的延迟,比如

主库的大事务有10分钟,那么从库就可能延迟10分钟

于是删除过多的数据,就可能导致大事务带来的延迟

(4).大表的DDL

对于大表的DDL,也会导致主备延迟

那么如何解决主备延迟来方便我们主从备份呢?

1.可靠性优先的策略

1.对于备库B现在的seconds_behind_master,如果小于一个值,则可以继续切换

2.主库设置为只读状态

3.判断备库上的seconds_behind_master的值,直到这个值变为0

4.备库B改为可读

5.切换到备库上

图片

这个流程的切换过程中,有一个双只读的状态,在此期间,是比较可怕的

因为整个系统无法对任何请求作出相应

2.可用性优先策略

上来就执行4 5步骤开始执行,也就是不等主备数据同步,直接连接到备库B,那么系统就没有不可用的时间了

这就是可用性优先的策略,但是会带来数据不一致的问题

假设有一张表

图片

这个表定义了一个自增主键id,初始化数据后,主库和备库都是3行数据,接下来插入两条语句的命令

图片

接下来在有延迟的情况下,插入一条数据后进行主备切换

图片

这里,我们使用了mixed来进行切换流程

那么上面的执行流程为

1.步骤2,主库A执行了insert语句,插入了一行数据(4,4),然后进行主备切换

2.主备之间具有五秒延迟,但是直接切换到了备库上

3.这就是备库上被插入了(4,5)

4.接下来收到了中转日志插入一个4,这时候就相当于插入了(5,4)

导致了主备不一致的情况

如果使用binlog为row的格式,那么不会出现的情况

因为row日志会记录新插入的行的所有的字段值,那么在不一致的情况下,会报出duplicate key error的错误

这时候只有一行数据不一致

图片

导致两者冲突,但是row格式更容易让数据

当然会有一些极端情况需要数据的可用性更加优先

比如说日志库,业务系统依赖于日志库写入,必须保证可用性,而且不一致可以通过binlog来补回来

但是可靠性优先的情况,发生了异常

假设,主库A和从库B的主备延迟是30分钟,主库A停电,HA需要切换,在切换的时候,可以等待小于5秒,但是现在不一样了

图片

这样直接切换到了备库B,中转日志没有应用完成,客户端连上B,会发现一部分数据丢失,对于一些业务,暂时丢失数据是不能被接受的

导致备库延迟的原因

1.备库执行日志的速度持续低于主库生成日志的速度,那么这个延迟会越来越大,备库永远都没法追上主库的节奏

图片

这是上面的主备复制的流程图

这里加上了两条黑色的箭头

一个是写入主库,一个是备库执行中转日志relay log

明显第一个的并行度更高

主库上,MySql上对于数据的更新有优化手段,各种锁,并且支持行锁,对于业务并发是很好支持的

但是在备库应用日志的时候,由于在5.6之前,只支持单线程复制,因此在主库并发高,TPS就会有严重的主备延迟

于是后来官方提供了多线程复制机制,就是将只有一个线程的sql_thread,拆成多个线程

图片

现在这个模型汇总,原本的sql_thread简化为了coordinator,其并不直接更新数据了而只是分发和中转日志,真正更新日志的是多个worker线程,而work的线程个数,由slave_parallel_workers决定的,可以设置为核的四分之一到二分之一之间

但是由于MySql必须保证数据的一致性,所以不能直接轮询的分发,因为如果对于同一行的数据的事务1,2执行顺序颠倒了,可能导致数据不一致

而且同一个事务的多个更新语句,也不能分为不同的worker来执行

所以多线程下执行sql,必须满足两个要求

1.同一事务不能拆开执行

2.更新覆盖,更新同一行的两个事务,必须分发到同一个worker中

于是有了一种思路

按表分发,如果两个事务更新不同的表,那么可以并行分发

如果是一个事务是跨表的,那么还是要放在一起考虑的

按表分发的策略

如果两个事务更新不同的表,就可以并行,保证worker之间不会更新同一个行

图片

每个worker对应一个hash表,表的key是库名表名 value是一个数字

表示队列中有多少个事务涉及修改这个表

有事务分配到这个worker上,涉及的表就会加到对应的hash表中,worker完成后,这个表会从hash表中去掉

图中的图表示hash_table_1 表示,现在 worker_1 的“待执行事务队列”里,有 4 个事务涉及到 db1.t1 表,有 1 个事务涉及到 db2.t2 表;hash_table_2 表示,现在 worker_2 中有一个事务会更新到表 t3 的数据。

假设现在有一个新的事务T,修改的行涉及到表 t1 和 t3

整体的分配就是

由于涉及到修改表t1,那么worker_1队列在事物修改标题时候,事务T和队列1中某个事务发生冲突

然后遍历发现worker2也冲突

然后就进入等待

如果worker2已经不冲突,只和worker1有冲突了,那么就会分配给worker1

总结下来说

如果和所有worker都不冲突,分配给最闲的

如果和多于一个worker冲突,那么会进入等待,直到只剩一个冲突

如果只和一个冲突,会分配给这个存在冲突的worker

这个表对于某个表的热点特别大的时候,就不好办了

按行分配的策略

按照行级锁的原则,只要不是同一行,就不会出出现冲突,但是binlog的格式必须是row

在按表分配的基础之上,把hash表的key改为了库名+表名+唯一键的值

但是如果还会出现唯一索引的冲突问题,然后可能导致主键索引在备库更新时候出现了冲突

于是事务中还是会出现库名 表名 主键 唯一键

于是按行分发的策略会消耗更多的计算资源

而且会要求这个表的binlog格式必须是row,而且具有主键

最重要的是不能有外键

在使用了按行分配的情况下

会很消耗内存,比如一个语句要删除100万行数据,就需要记录100万行

消耗CPU,解析binlog,计算hash值

所以并不能单独使用

MySql5.6版本的并行复制

在MySql5.6版本中,支持了按照库来并行,如果一个MySql有多个DB,并且多个DB压力均衡

而且支持任何binlog

但是如果所有的表都在一个库里就不能行了,可以创建不同的DB,将相同热度的表均已分配到不同DB中,才能使用这个策略

MariaDB的并行复制

利用了redo log的group commit优化,而MariaDB的并行复制策略就是利用了这个特性

能够在同一组提交的事务,不会修改同一行

所以利用这个特性,进行一组的复制

一组里一起提交的事务,有一个相同的commit_id,下一组就是commit_id+1

commit_id直接写到binlog里面

穿到备库应用,相同commit_id的事务分发到过个worker执行

执行之后去取下一批

但是备库上的并发执行和主库还是有一定的距离的

主库上可以

图片

从库上只可以

图片

可以看出,要等第一组的事务执行完成后,第二组事务才能开始执行,不过仍然是一个很漂亮的创新

后来MySql5.7也提供了commit_id复制

可以设置slave-parallel-type来控制并行复制策略

1.配置为DATABASE,表示使用MySQL5.6版本的按库并行

2.LGICAL_CLOCK,按照类型MariaDB策略,不过MySQL5.7这个策略,针对并行进行了优化

MariaDB是策略核心是所有commit的事务已经不会发生冲突

图片

但是可以在redolog commit之前就进行动手脚呢,也就是redo log prepare阶段

只要能够到达 redo log prepare 阶段,就表示事务已经通过锁冲突的检验了。

于是在处于perpare状态的事务,和处于commit状态的事务,在备库执行的时候,是可以并行的

后来又提供了binlog-transaction-dependency-tracking参数,可选值有以下三种

1.Commit_ORDER 即为上面的进入prepare可以并行的策略

2.WRITESET,对于事务设计的每一行进行计算hash值,这个 hash 值是通过“库名 + 表名 + 索引名 + 值”计算出来的。如果一个表上除了有主键索引外,还有其他唯一索引,那么对于每个唯一索引,insert 语句对应的 writeset 就要多增加一个 hash 值。

组成集合 writeset。如果两个事务没有操作相同的行,也就是说它们的 writeset 没有交集,就可以并行。

3.WRITESET_SESSION 在上面的基础上,保证了主库上同一线程执行的两个事务,备库上也要保证先后顺序

发表评论

邮箱地址不会被公开。 必填项已用*标注