17.2 发布订阅、流处理与事件链路观测
赛事结束十分钟了,排行榜还是旧分数。值班工程师看到 broker 在线,却不知道消息停在 Outbox、消费队列,还是投影更新里。
事件系统不只有一种形态。通知型发布订阅关心“事实发生后谁要响应”,流处理关心“持续事件序列如何被变换、聚合和物化”。两者都使用 broker,不代表状态和时间语义相同。
发布订阅与持久流
| 维度 | 通知型 Pub/Sub | 持久事件流 |
|---|---|---|
| 主要目标 | 广播事实、触发反应 | 保存有序记录、持续计算 |
| 消费位置 | 常由队列确认控制 | 常由 offset/checkpoint 控制 |
| 重放 | 可能有限 | 通常是核心能力 |
| 状态 | 多在消息基础设施之外 | 处理器常维护状态存储 |
| 典型用途 | 邮件通知、缓存失效 | 排名聚合、实时风控、指标管道 |
具体产品可能同时支持多种语义,设计应围绕业务需要,而不是围绕产品标签。
流处理必须区分时间
- Event time:事实在源系统发生的时间;
- Processing time:处理器实际处理的时间;
- Ingestion time:平台接收事件的时间。
网络延迟和离线客户端会让事件乱序。按 event time 计算“每五分钟得分”时,需要 watermark 表示事件时间进度的估计,并定义迟到事件策略。它不是“之后绝不会迟到”的证明。Flink 多输入算子通常由活跃输入中最慢的 watermark 限制进度;空闲输入若没有正确标记,会拖住所有窗口。可以:
- 允许一定迟到并更新旧窗口;
- 把过晚事件送入修正流;
- 只追加更正结果,不静默丢弃;
- 对外展示结果的最终化程度。
例如窗口为 [10:00, 10:05),Flink 事件时间窗口允许迟到 2 分钟。watermark 推进到 10:05 后可先输出结果;若在 watermark 为 10:06 时收到事件时间 10:03 的新记录,仍可更新窗口。等 watermark 已到 10:08,再收到这条记录,就需要配置的迟到侧输出或业务修正流程。这里讨论的是 watermark 的值,不是墙上时钟走了几分钟。
更新窗口可能多次输出同一窗口的聚合结果。下游应按 (playerId, windowStart) 更新结果或处理撤回,不能把每次结果都当新增积分累加。Flink 的默认允许迟到时间为 0,超出保留边界的记录默认丢弃;上述修正策略需要显式配置,不能只写进设计文档。
窗口结果何时算“最终”是业务决定,不只是框架参数。
有状态处理需要可恢复 checkpoint
一个排名聚合器维护玩家分数时,状态、输入 offset 和输出必须协调:
完成 checkpoint:保存一致的输入位置、算子状态和所需 sink 提交信息
故障恢复:还原状态 → 从记录的位置重放 → sink 按协议提交或去重这是恢复关系示意,不是手写四个顺序调用就能完成的协议。框架通过快照或事务协调恢复边界,输入源必须能从保存的位置重放;能否让外部输出只生效一次,还取决于 sink 是否参与提交协议或支持幂等写入。只给普通 HTTP 请求外面套一个 checkpoint,不会让对方自动记住上次已经扣过款。
升级处理逻辑前要判断:旧状态能否兼容新 schema,是否需要从原始流重建,以及重建会不会压垮下游。
Choreography 不等于没有流程责任
多个消费者围绕事件协作时,仍需有人拥有端到端结果。至少维护:
- 流程图和事件所有者;
- 每一步 SLA 与失败语义;
- 相关 ID 和业务状态查询;
- 告警、补偿和人工操作入口。
若一个关键流程需要阅读十个消费者源码才能理解,应该引入流程视图、状态投影,或改为显式编排。
事件链路如何追踪
同步调用也需要插桩显式传播上下文,调用栈本身不会跨进程。异步事件跨越时间和进程,需要区分:
- 追踪上下文:例如 W3C
traceparent中的 trace ID、父 span ID 和标志;仅有traceId无法恢复完整父子关系; correlationId:同一业务流程;causationId:哪个命令或事件直接导致当前事件;eventId:当前事件的唯一身份。
生产者应向消息属性或约定信封字段注入上下文,消费者提取它并创建处理 span。批量消费或一个结果来自多条消息时,可以用 span links 表达多个因果来源,具体遵循所用 OpenTelemetry 插桩约定。
不要把所有事件强行塞进一个永不结束的 trace。长流程可以用 correlation 连接多个 trace,并用业务时间线展示状态。
关键指标
基础设施在线不代表事件系统健康。至少观察:
- 生产速率、消费速率和 lag;
- 最老未处理事件年龄;
- 端到端处理延迟,而不只是 broker 延迟;
- 重试、死信、重复和乱序数量;
- 分区倾斜与热点键;
- 投影版本、重建进度和业务对账差异。
offset lag 是位置差,不等于等待秒数;不同消息耗时也可能差很多。occurredAt 到完成时间还会混入源时钟偏差,要同时保留接收时间与阶段耗时。
告警应关联用户影响。例如 lag 增长但仍在业务新鲜度目标内,可以是容量预警;关键结算事件超过截止时间则需要立即响应。
上线前故障演练
- 消费者在业务提交后、确认前崩溃;
- 单个分区出现永久坏消息;
- 事件晚到、重复和跨版本混合;
- broker 暂时不可用导致 Outbox 堆积;
- 重建投影时新事件持续进入;
- 热点聚合键压垮单分区。
有了这些恢复路径,团队才能发现并修复故障,不必等排行榜迟迟不更新,才知道哪一步出了问题。
参考资料
- Apache Kafka, Kafka Streams Architecture
- Apache Flink, Timely Stream Processing
- Apache Flink, Windows and allowed lateness
- OpenTelemetry, Messaging spans
- OpenTelemetry, Messaging semantic conventions