第5章 初始快照:在数据继续变化时开始同步
第一次接入为什么需要快照
数据库已经存在两千行,连接器此时才开始读取日志。如果只订阅未来变化,长期未更新的商品永远不会进入查询系统。初始快照解决这个起点问题:读取一份已有数据,并把后续日志接到这份数据之后。真正困难的地方是读取期间业务不能长期停止,快照与日志必须围绕一致性边界协调。
Debezium MySQL 初始快照会建立相应的读取和日志位置关系,具体锁与事务流程受版本及配置影响。本书使用连接器内置 initial 模式,没有自行实现数据库复制协议。我们验证该版本在当前权限、ROW 日志与单表条件下的最终结果,不把一段教程伪代码当成所有版本的完整内部实现。
制造一个可以证明的交叠窗口
普通两千行查询可能在几十毫秒内完成,后台同时启动写入并不能证明它真的与快照交叠。随书连接器为 products 指定快照查询覆盖,在 WHERE 中调用 SLEEP,使每行读取有小幅延迟。这个查询只用于实验,不推荐在生产表上照抄,也不依赖它提升数据一致性。
控制程序轮询源库 SHOW FULL PROCESSLIST,直到看到连接器正在执行带 SLEEP 的真实 SELECT。它记录查询身份和源库 UTC 时间,然后在独立业务连接中插入一行、更新一行、删除一行。写入结束后再次查看相同查询仍然活跃。前后观测与时间戳保存为 snapshot-write-overlap.json,检查要求两个观测都存在。
这份证据说明写入发生在实际快照扫描仍然执行的时间窗口内,而不只是发生在任务名为“snapshot”的步骤附近。查询已开始,连接器读取事务的快照边界已经由其内置流程建立;实验不声称从 processlist 可以直接读取 InnoDB 内部 read view 的全部内容。最终的完整对账才负责验证快照和增量衔接没有遗漏这些变化。
观察读事件与日志事件
连接器快照事件的 op 为 r,持续日志中的创建、修改和删除分别表现为 c、u、d。集成要求四种操作都实际出现,不能只看到最终行数相同就宣布验证了所有路径。数据消息中的 source 还包含真实 binlog 文件和位置,我们保存样例以确认链路读取的是真实日志。
假设 id 二在快照中被读到,稍后又被业务删除,消费者最终需要把它标记删除。假设 id 一的快照值还是旧价格,随后更新事件会覆盖成新价格。插入 id 三千零一可能通过日志进入数据流。连接器内部具体事件时序必须以实际记录为准,我们不要求从某个偶然排序推导全局序列。
快照完成不等于全链路完成
连接器结束表扫描后,Kafka 可能仍有待投递记录,消费者也可能尚未写完投影。因此不能拿快照完成日志直接向业务宣布查询已经可切换。我们等待消费者读取并提交,随后在源库写入停止的条件下逐行比较全部字段。只有比较相等,才能说该批有限数据已经收敛。
本实验消费者通过消息空闲窗口结束一次有限运行,这是一种测试驱动方式,不是生产流处理生命周期。若机器变慢导致事件晚到,对账会失败而不是把空闲当成正确。生产消费者通常持续运行,并用明确的水位和业务探针判断追平程度;不能把三秒无消息当成永久没有新变化。
快照负载与生产选择
全表读取会消耗数据库 IO、缓存和网络,也可能延长事务存活时间。对大表安排快照窗口时,要评估并发写入、日志保留与磁盘余量,而不是只调高 fetch size。增量快照、只读副本接入或分批初始化都是可讨论方案,但各有一致性与版本要求,本实验没有把它们作为已验证功能。
快照查询覆盖还会改变选取范围,错误过滤条件可能永久漏掉对象。如果生产必须过滤租户或软删除行,应明确快照过滤与持续变更过滤是否具有相同语义,否则初始数据和后续数据会来自两个不同集合。本书读取全部商品,包括 active 为零的对象,把是否展示留给查询契约。
本章练习
删除人为 SLEEP 后再运行实验,解释为什么交叠检查可能失败,且这个失败不能证明连接器快照算法错误。再设计一种不依赖固定等待秒数的替代同步点,写出它能够证明的边界。