第10章 消费停机与积压:恢复速度必须快于变化速度
先区分是谁停止了
业务源库继续写入时,消费者可以停止,连接器也可以停止,broker 还可能不可用。三者形成的积压位置不同。消费者停止而连接器正常,变化主要积压在 Kafka;连接器停止而数据库继续写入,需要依赖 MySQL 日志仍然保留;broker 不可用则可能让连接器无法交付,同时增加源日志追赶压力。一个统一的“同步延迟”数字无法解释这些不同恢复路径。
本章真实实验只停止消费者,数据库、Connect 和 Kafka 继续运行。控制程序在没有消费者进程存活的阶段,对一百个对象执行价格递增与 revision 更新,再删除一个对象并插入另一个对象。它先比较源表与 live 投影,要求两者确实不同,证明查询副本在停机期间保持旧状态。随后才重启消费者读取积压。
为什么停机前后都要检查
如果只启动消费者并检查最终相等,测试可能因为没有成功产生写入而轻易通过。我们增加“停机时可观察到旧数据”的断言,再要求恢复消费报告包含至少一百条更新和一条删除,最后执行完整对账。这三步分别证明故障造成影响、真实变化进入流以及结果已经修复。
这里的一百条更新来自一百次真实源库提交,连接器从 binlog 捕获。它们不是直接塞入 Kafka 的测试消息,也不是在 PostgreSQL 中手工改好结果。这样消费者停机与追平路径经过了全部真实组件,能够暴露连接器过滤、消息形状和投影逻辑的问题。
用流量关系解释追平
设源数据变化进入 Kafka 的平均速率为每秒一百条,消费者恢复后只能处理每秒八十条,即使所有服务都显示运行中,积压也会继续增加。只有恢复处理能力大于持续输入,积压才会下降。追平时间粗略取决于积压量除以净处理能力,但峰值、批处理和数据库锁会让真实曲线偏离简单估算。
因此恢复容量要预留余量。平时刚好满负荷的消费者,一旦停机就没有追赶空间。增加实例也不一定有效:单个分区最多由一个组成员消费,本书只有一个数据分区,增加同组进程不能把该分区平均切成多份。容量设计必须同时考虑分区、键顺序、数据库写入与位点提交成本。
lag 是交付指标,不是内容正确性
Kafka lag 通常比较日志末尾与消费组提交位置。消费者提前提交位点会让 lag 看起来很低,投影却可能缺行;消费者事务很慢,lag 较高也可能只是延迟。连接器尚未把源日志写入 Kafka 时,消费组 lag 甚至可能为零,而源库仍有未交付变化。因此 lag 适合定位交付进度,需要与源探针和对账配合使用。
本实验报告消费消息数、操作种类和完整内容比较,没有把一轮总耗时当成稳定性能基准。生产可增加源提交时间、连接器时间与投影可见时间的差值分布,观察长尾而不是只看平均值。时间字段还会受到机器时钟偏差影响,应结合事件身份与日志位置分析。
恢复过程中不要扩大故障
积压追赶会突然提高投影写负载,可能挤压线上查询。如果让消费者无限并发,数据库连接、事务日志与磁盘都可能成为新瓶颈。更合理的做法是限制批量、连接数和最大写速率,观察查询延迟后逐步提速。限速使追平更慢,但能够保住主要业务,取舍需要写入恢复目标。
如果 Kafka 留存不足,恢复时所需事件可能已经被清理。auto.offset.reset=earliest 只会从仍然存在的最早记录开始,不能恢复已消失的历史。生产应当检测位点越界并明确选择重建,而不是把“成功读到一些消息”误认为“缺口已经补齐”。本实验在短时停机内完成,没有模拟 broker 数据过期,相关策略属于设计要求。
结束恢复需要业务验收
当积压接近零,还应检查删除对象是否消失、新插入对象是否存在、关键字段是否正确。我们的逐行核对覆盖所有商品,生产可以先运行重要对象探针,再分段进行全量对账。只有交付进度和内容检查都满足要求,才能结束恢复事件,避免下一次用户查询才发现残留错误。
重试应当有可观察的代价
投影暂时不可用时重试是合理动作,但重试速率需要受控。无限快速重连会消耗数据库连接和日志,也会挤占同机其他业务。生产客户端应区分暂时故障与契约错误,使用退避和告警;解析不支持事件不应每秒重启一次而永远不通知维护者。本书有限集成遇错即失败,便于留下清楚现场,尚未实现长期运行的退避控制器。扩展前应保留这种错误可见性。
本章练习
假设停机一小时产生三十六万条积压,持续输入每秒一百条,恢复能力每秒三百条,估算理想追平时间。随后列出至少四个会让实际时间更长的条件,并说明哪些条件可以通过增加消费者实例解决。