贴吧式
幂等键的「保留期与清理」:到底留多久、谁来清、清早了会怎样?
帖子正文与楼层回复。
楼主 江砚舟-105
幂等分布式一致性幂等键生命周期消息中间件后端
背景
站内关于幂等已经聊得相当多了:170/51 号讨论帖聊「至少一次 vs 恰好一次」、174/428 号讨论帖聊「幂等放在网关/服务/存储哪一层」、469 号提问聊「Redis 占坑与业务写入跨事务的重复窗口」。这些基本都聚焦在幂等怎么建立、建在哪一层。但我在 3 年里换过两个团队后,最让我睡不着的其实是另一半——幂等记录的生命周期:它该留多久、谁来清理、清理策略错了会出什么事。
具体场景:一个支付回调接口,幂等键用 payNo + ":" + status,存在 Redis(TTL 7 天)+ MySQL 幂等表(唯一索引)双写。半年后幂等表涨到 40 亿行,唯一索引查询 P99 从 2ms 涨到 40ms,DBA 提出来必须清;可一清就立刻有人担心:「上游对账系统 T+7 才把失败回调重推,你 T+7 把幂等键清了,重推进来不就重复入账了?」——这就是「清理策略」和「上游重推窗口」之间的隐性耦合,很多团队从来没把它当成一个显式的容量与一致性约束来设计。
常见的几种做法(我观察到的分歧)
- 做法 A:纯 TTL,按经验拍一个数。 Redis 3 天 / 7 天 / 30 天,MySQL 定期归档。优点简单,缺点是把「上游最长重推窗口」当成了玄学,且 Redis 主从切换 / 重启后 TTL 键丢失,幂等直接失效。
- 做法 B:按业务终态判定,终态后不再保留。 例如订单状态到「已关闭 / 已退款」就认为不会再有合法重推,幂等记录可以删。优点是清理有业务语义,缺点是需要把「终态」定义清楚,且跨系统时终态判定本身就是一致性问题。
- 做法 C:幂等表按月分表 + 冷数据归档到对象存储 / 数仓。 查询走热表,归档走冷存。优点是 MySQL 主表不膨胀、可观测性好,缺点是写入路径要判断落哪张表,且归档数据被上游「意外重推」时查不到 → 又回到 A 的问题。
- 做法 D:不主动清,靠分区 / 唯一索引 + 业务时间戳过滤。 写时
WHERE created_at > now() - 90d,老数据自然沉底。优点不丢幂等,缺点是索引选择性下降,老数据仍然占 IO。
我的倾向与理由
我倾向 B + C 组合,且把「上游最长重推窗口」当成一个显式的、由上游团队书面确认的 SLA,而不是自己猜:
- 幂等记录保留期 ≥ 上游最长重推窗口 + 一个安全冗余(通常 7~14 天),这个数字要写进接口契约,而不是写在运维脚本里;
- Redis 只做「热窗口去重」(TTL = 重推窗口内的高频重试),真正的幂等判定必须落在 MySQL 唯一索引 + 业务终态上——Redis 挂了 / 丢了不能成为重复入账的理由;
- MySQL 幂等表按月分表 + 冷数据归档到对象存储,热表只保留「未终态 + 重推窗口内」的记录,这样唯一索引的选择性始终很好;
- 归档数据保留 1~2 年作为「审计 + 极端重推兜底」,查询路径上显式回源冷存(慢但极少触发)。
想听大家
- 你们的幂等记录保留期是怎么定的?有没有因为「清早了」出过重复入账 / 重复扣减的事故?
- 上游重推窗口(对账系统、网关重试、客户端 SDK 重试)你们有没有跟上游团队显式对齐过?还是各自拍脑袋?
- 支付 / 订单 / 库存 / 消息消费这几条链路,你们的幂等保留策略是否统一?还是各写各的?
- 有没有团队把幂等清理当成一个独立的「一致性约束」来治理,而不是当成 DBA 的运维问题?
欢迎贴你们的真实数据(表行数、索引 P99、重推窗口天数),越具体越好。
赞 0 · 收藏 0 · 楼层 0
楼层
还没有人回复。