前面提到的场景里,不论是偶发的查询压力,还是备份,对备库延迟的影响一般都是分钟级的,而且在备库恢复正常以后都能够追上来。
但是,如果备库的执行日志的速度持续低于主库生成日志的速度,那这个延迟就有可能成了小时级别。而且对于一个压力持续比较高的主库来说,备库很可能永远都追不上主库的节奏。
备库并行复制能力。
主备流程图:
备库日志执行比主库生成日志慢,意思就是图中上面的黑色箭头比下面黑色箭头粗。
如果备库sql_thread用单线程的话,就可能会导致备库应用日志不够快,造成主备延迟。
在官方的5.6版本之前,MySQL只支持单线程复制,由此在主库并发高、TPS高时就会出现严重的主备延迟问题。
多线程复制,就是要把图中只有一个线程的sql_thread,折成多个线程,符合下面的模型:
work线程的个数,由参数slave_parallel_workers决定的。一般这个值设置为8~16之间最好(32核物理机的情况),备库还有可能要提供读查询,不能把CPU都吃光了。
coordinator在分发任务的时候,需要满足以下两个基本要求:
- 不能造成更新覆盖。这要求更新同一行的两个事务,必须被分发到同一个worker中。
- 同一个事务不能被拆开,必须放到同一个worker中。
MySQL 5.5版本的并行复制策略(非官方版本)
按表分发策略
思路:如果两个事务更新不同的表,它们就可以并行。因为数据是存储在表里的,所以按表分发,就可以保证两个worker不会更新同一行。当然,如果有跨表的事务,还是要把两张表放在一起考虑。
按行分发策略
思路:如果两个事务没有更新相同的行,它们在备库上可以并行执行。这个模式要求binlog格式必须是row。
相比按表分发策略,按行并行策略在决定线程分发的时候,需要消耗更多的计算机资源。
MySQL 5.6版本的并行复制策略(官方)
官方MySQL 5.6版本,支持了并行复制,只是支持粒度是按库并行。
这个策略的冰箱效果,取决于压力模型。如果在主库上有多个DB,并且各个DB的压力均衡,使用这个策略的效果会很好。
相比按表和按行分发,这个策略有两个优势:
- 构造hash值的时候很快,只需要库名;而且一个实例上DB数也不会很多,不会出现需要构造100万个项的情况。
- 不要求binlog的格式。因为statement格式的binlog也可以很容易拿到库名。
但是,如果主库上的表都放在同一个DB里面,这个策略就没有效果了;或者如果不同DB的热点不同,也起不到并行的效果。
MariaDB的并行复制策略
利用redo log组提交特性:
- 能够在同一组里提交的事务,一定不会修改同一行;
- 主库上可以并行执行的事务,备库上也一定是可以并行执行的。
实现上,MariaDB是这么做的:
- 在一组里面一起提交的事务,有一个相同的commit_id,下一组就是commit_id+1;
- commit_id直接写到binlog里面;
- 传到备库应用的时候,相同commit_id的事务分发到多个worker执行;
- 这一组全部执行完后,coordinator再去取下一批。
存在的问题:并没实现“真正的模拟主库并发度”这个目标。在主库上,一组事务在commit的时候,下一组事务是同时处于“执行中”状态的。但是这个策略,在备库上执行的时候,要等第一组事务执行完后,第二组事务才能开始执行,这样系统的吞吐量就不够。
统一被大事务拖后腿。
MySQL 5.7的并行复制策略
类似MariaDB的功能,有参数slave-parallel-type来控制并行复制策略:
- 配置为DATABASE,表示使用MySQL 5.6版本的按库并行策略;
- 配置为LOGICAL_CLOCK,表示的就是类似MariaDB的策略。不过,MySQL 5.7版本这个策略,针对并行度做了优化。
优化思路:
- 同时处于prepare状态的事务,在备库执行时是可以并行的;
- 处于prepare状态的事务,与处于commit状态的事务之间,在备库执行时也可以并行。
MySQL 5.7.22的并行复制策略
基于WRITESET的并行复制
新增了一个参数binlog-transaction-dependency-tracking,用来控制是否启用这个新策略:
- COMMIT_ORDER,表示的就是前面介绍的,根据同时进入prepare和commit来判断是否可以并行的策略;
- WRITESET,表示的是对于事务涉及更新的每一行,计算出这一行的hash值,组成集合writeset。如果两个事务没有操作相同的行,就是说他们的writeset没有交集,可以并行;
- WRITESET_SESSION,是在WRITESET的基础上多了一个约束,即在主库上同一个线程先后执行两个事务,在备库执行的时候,要保证相同的先后顺序。