知识卡片
采集网关的三层可靠性保障:在Kafka不提供exactly once的前提下实现at least once
内容
Kafka本身在设计和通信协议上并不提供强一致性保证(exactly once),面对这个约束,某音乐公司的数据采集网关通过多层配合设计,尽量逼近可靠投递的效果。第一层保障来自客户端:数据先发往采集网关,网关收到数据后立即返回成功,由网关负责确保数据最终发往Kafka;如果SDK端没有收到成功响应,SDK会主动重试(但JS、Flash这类端没有重试逻辑,属于已知的能力局限)。第二层保障来自网关和Kafka通信这一环:每次向Kafka发送数据后,实际结果会落入三种状态之一——Kafka写入成功、可以安全重试(Kafka确定没收到)、不可安全重试(Kafka可能写入了也可能没写入,比如网络超时或者迁移partition期间的Request Timed Out);针对这三种状态网关采取差异化处理:可以安全重试的数据先在网关本地缓存(优先用Redis,Redis不可用时降级到磁盘),再重新发往Kafka;不可安全重试的数据则被送进一个专门的容错topic,而不是简单地重试(因为重试可能造成重复写入)。通过这套组合策略,正常业务只需要读取普通topic就能获得at most once(最多一次成功)的保障,再叠加上容错topic里的数据,整体上能做到at least once(至少一次成功)的保障。这套设计再配合网关自身上报的监控数据(收到量、成功发送量、本地缓存量),运维人员可以根据每个topic的量级判断整个系统的运行状况——一旦发现容错topic里堆积了大量数据,就说明网络出现了长时间抖动或者Kafka出现了宕机,需要针对性处理。这个案例展示了一条在底层组件本身不提供强一致性保证时,如何通过在应用层组合多种手段(客户端重试、网关本地缓存兜底、按结果状态分流到不同topic)去逼近更强可靠性目标的实践思路——核心是先把每一种可能的失败结果精确分类(成功/安全重试/不可安全重试),再针对每一类设计专属的处理路径,而不是用一套笼统的重试逻辑应对所有失败情况。
结构图:
flowchart TB
A["SDK/客户端发送数据"] --> B["数据采集网关"]
B -->|"收到即返回成功"| A
A -->|"未收到成功响应"| A2["SDK端重试\n(JS/Flash端无此能力)"]
B --> C["网关向Kafka发送数据"]
C --> D{"发送结果状态"}
D -->|"Kafka写入成功"| E["完成"]
D -->|"可安全重试\n(Kafka确定未收到)"| F["网关本地缓存\n(优先Redis,降级磁盘)"]
F --> C
D -->|"不可安全重试\n(网络超时/迁移超时)"| G["写入容错topic"]
E --> H["正常topic\n提供at most once保障"]
G --> I["容错topic\n叠加提供at least once保障"]