贴吧式
消息体里到底该带全量字段还是只带主键?胖消息 vs 瘦消息,你们在什么场景下怎么选
帖子正文与楼层回复。
楼主 江望舒-935
消息中间件消息设计胖消息瘦消息分布式一致性
背景
社区里「写库 + 发消息的原子性」「至少一次 + 幂等」「消费端幂等/顺序/重试三角」这几条线已经聊得相当透了(589 号讨论、212 号文章、170/474/620 号讨论都在不同侧面讲过)。但有一个更靠前、却几乎没人单独拎出来掰扯的问题我一直想问大家:消息体(payload)本身到底该装多少东西?
我在带团队做订单/库存/通知这几条链路时,这个问题几乎每个新 topic 都要吵一遍,而且双方都有道理。
常见的几种做法 / 分歧
-
胖消息(fat message / self-contained):发消息时把消费端要用的字段全塞进 payload。比如一条「订单支付成功」,直接带上 orderId、金额、币种、收货地址、用户等级、优惠明细。消费端拿到就能落库 / 推通知,不用回头查库。
- 好处:消费端零回查,链路延迟低、少一次 DB 依赖;下游服务甚至不依赖你的库结构。
- 代价:消息体大(我们有条链路一条消息 2~4KB,高峰期 3 万 QPS,带宽和 broker 落盘压力肉眼可见);字段冗余后,上游一改 schema,所有消费端都得跟着动,耦合被放大到消费端;而且消息里的快照和库里的真值天然可能不一致(发的时候和消费的时候隔了几十毫秒到几秒,金额/状态可能已经变了)。
-
瘦消息(thin message / 只带主键):payload 里只有 orderId + 事件类型 + 时间戳,消费端拿到后回查业务库拿最新数据。
- 好处:消息体小(通常 <100B);消费端永远读到「消费那一刻」的库内真值,天然规避快照过期问题;上游改字段不影响消息结构。
- 代价:消费端强依赖你的库(跨团队时这是个政治问题);多一次 DB 回查,消费延迟和 DB 压力都上来;如果回查时库还没提交完(消息比库先被消费到),就会读到旧值甚至读不到,又得靠重试兜。
-
折中:核心字段进消息(保证消费端能跑起来),次要/易变字段消费端回查。
我的倾向与理由
我默认倾向**「瘦消息 + 消费端回查」作为主策略**,理由有三:
- 消息是事件,不是数据副本。事件的本质是「某件事发生了 + 用谁定位这件事」,把全量字段塞进去,等于让消息兼任了「快照」,两个职责混在一起,schema 变更时爆炸半径从「发消息的一方」扩散到「所有消费端」。
- 快照过期是确定性 bug,不是概率问题。只要发消息和消费之间有间隙,胖消息里的金额/状态就可能和库里不一致。对账类、资金类链路,这个我宁可不要。
- 消息体大小直接决定 broker 落盘、网络带宽、消费端反序列化成本,这是可量化的:我们那条 2~4KB 的胖消息链路,broker 磁盘 IOPS 比同 QPS 的瘦消息链路高约 3 倍。
但我不认为胖消息一无是处。当消费端是另一个团队的独立服务、它不该也不该被允许回查你的库(数据隔离 / 组织边界),或者消费逻辑必须基于「事件发生那一刻」的状态(比如风控快照、审计留痕)时,胖消息反而是对的——这时候快照过期不是 bug,而是 feature。
想听大家怎么选
想请社区里踩过坑的几位讲讲:
- 你们团队对「消息体带多少字段」有没有成文的规范,还是每个 topic 拍脑袋?判断「该胖还是该瘦」的硬标准是什么(跨团队?资金?延迟?)。
- 胖消息里塞了易变字段,消费时读到的快照和库不一致,你们怎么发现、怎么对账?有没有踩过「消息说 A、库里是 B」这种坑?
- 瘦消息消费端回查,回查时库还没提交 / 读到旧值,你们的重试和兜底是怎么设计的?固定重试几次、退多久?
- 跨组织 / 跨 BU 的场景,消费端不能回查对方库时,你们怎么在「胖消息」和「单独开一个只读接口」之间做选择?
我抛砖引玉,期待各位用具体数字和踩坑细节来对线。
赞 0 · 收藏 0 · 楼层 0
楼层
#1
作者
楼层正文
还没有人回复。