个人随记 —— MySQL 数据同步方案思考
背景
在灾备、读写分离等数据同步场景中,同步延迟越低,越能应用在更多场景之中,RPO 和 RTO 最好能无限趋近于 0。
但是这需要下游数据库的平均吞吐能力大于上游平均吞吐能力。实际上一般备集群、只读集群的规格其实是小于主集群的,所以在并发度较低时,提升并发度是提升吞吐的有效手段。
MySQL 官方能力
MySQL binlog 文件记录数据的修改操作,官方提供 mysqlbinlog 程序用于解析 binlog 文件
$ mysqlbinlog <binlog-filename> --read-from-remote-server --stop-never --base64-output=DECODE-ROWS --verbose --host=<MySQL-host> -u <MySQL-user> -p
- binlog 记录了数据变更的二进制数据,是可以高效进行异构数据同步源头。
- 日志文件按照偏移量顺序记录数据修改,某一个时刻的日志文件内容反映了此时 MySQL 串行化的事务执行历史
MySQL 5.7 中 Logical clock 同步方案
binlog 文件中事务会按顺序分配递增的序号 sequence_number,每个事务会记录需要等待的事务的最大序号 last_committed,以此就可以表示逻辑上的先后关系。
如图所示,我们就可以按照 last_committed 来构建先后以来顺序,对不依赖的事务尽可能的执行并发。
MySQL 8.0.1 中 Write set 同步方案
如果两个事务在时间上有先后关系,但是实际上实际上两个事务之间并没有修改相同的数据,那么我们也可以将这两个事务并发执行,但是这个将会打破源库写入的实际情况。所以在 MySQL 8.0.1 中基于此提出了 WRITESET 和 WRITESET_SESSION 方案,可以进一步提升并发。MySQL 会基于事务的写集计算先后关系并同样记录在 sequence_number 和 last_committed 中,详见博客
可以看到上图中的事务 T4 与 T6 在时间上存在先后关系,但它们所修改的数据并不重叠,因此下图中将它们并发同步。
两种方案优劣势比较
方案 | 优势 | 劣势 |
---|---|---|
Logical clock | 基于时钟的方案从并发的定义出发,是各种方案中最安全的 | 性能取决于备集群规格、负载,没有特别多的优化空间 |
Write set | 相对于前者,性能会有提升 | 无法确定客户端是否允许它们并发。如果客户端不允许它们并发,下游就会暴露异常的事务执行历史 |
并发度更高的方案
按行粒度并发同步
那其实我们可以直接跳过事务,按照行粒度来设计并发修改方案,在这里可以考虑
- 同一条记录的数据修改
- 两条记录如果因数据库约束存在先后联系
举个例子:
CREATE TABLE `test` (
`id` int(11) NOT NULL,
`name` int(11) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `c` (`name`)
);
修改记录如下:
--- M1
INSERT (1, 1)
--- M2
INSERT (2, 2)
--- M3
UPDATE (1, 1) TO (3, 3)
--- M4
INSERT (1, 4)
--- M5
DELETE (2, 2)
--- M6
UPDATE (1, 4) TO (2, 1)
--- M7
INSERT (5, 5)
具有相同取值的数据修改,需要保持先后关系:
行粒度同步的特点
- 通过行粒度拆分事务,实现比上游更大的并发度,从而可以有更多的可能性。
- 以行为粒度进行攒批,可以更容易获得稳定的批处理大小。
- 对事务进行拆分后,如果遇到同步中断,需要从低水位(low watermark)位置恢复同步,并对中断时低水位到高水位(high watermark)之间的数据修改使用合适方法处理潜在的重复同步。
- 无主键表较难处理中断恢复问题。
一些其他关键想法
行粒度数据同步放弃了查询下游数据库时的事务原子性,并且在多条记录之间引入了不符合因果关系的状态,但是我们可以引入一些写屏障进行切割隔离,使得可以在一定程度缓解这种问题