连载中 12/15

数据对账:Redis 与 DB 的最终一致

2026-09-08 · 285 阅读 · 0 评论 · 0 赞

为什么会对不上

预扣链路上有三个存储:Redis 记资格、MQ 传消息、DB 记订单。每个环节都设计了失败出口——落库失败回补、超时关单回补、消息重投——但每一个失败出口同时也是差异的来源:回补消息丢了就是少卖,重复回补就是超放,落库成功但标记没打上就是重复回补。机制的目的是把差异概率压到极低,对账的目的是证明它真的低——没有对账的一致性,都是自我感觉良好。

恒等式:对账的基本量

秒杀库存有一条恒等式:总库存 = Redis 剩余 + 已成订单占用 + 已回补 + 预扣未成单(在途)。对账就是活动各阶段核对这个等式:预热后核一遍(剩余=总量),开抢中周期核(差异=在途量级),结束后终核(差异应为零)。等式右边每一项都要有流水可查:扣减流水、订单记录、回补记录、在途快照——对账的前提是记账,流水不齐,账就没法对

实时对账与离线对账

两套节奏分工明确:实时对账——开抢进行中抽样比对(如每分钟抽 1% 的订单核对 Redis 与 DB 状态),发现系统性偏差立刻告警,是活动中的保险丝;离线对账——活动结束后 T+1 全量核对流水与恒等式,产出差异清单进人工处理,是最终裁决。实时要快、离线要全,两者的成本预算完全不同,别混在一起做。

// 终态核对:恒等式逐项核对
long total       = activityRepo.totalStock(skuId);        // 总库存
long redisLeft   = redis.sumBuckets("stock:" + skuId);    // Redis 各桶剩余
long orderUsed   = orderRepo.countPaid(skuId);            // 已成订单
long refunded    = refundRepo.countRefunded(skuId);       // 已回补
long inFlight    = deductRepo.countPending(skuId);        // 预扣未成单

if (total != redisLeft + orderUsed + refunded + inFlight) {
    alarmAndDrill(skuId, diffDetail());   // 对不上:出差异明细进人工
}

差异修复的分界线

对账发现差异后,能不能自动修?分界线是有没有明确的事实源:订单库是事实源——Redis 显示扣了、DB 无订单且回补记录缺失,可以放心按"回补缺失"自动补 Redis;反过来,两边都没记录的差异(未知来源的库存偏差),自动修复等于用猜测覆盖事实,必须进人工。自动修复的每一条都要留下修复流水,下次对账可复核——修复本身也要可对账。

对账是通用能力

把这套能力沉淀成平台,收益远超秒杀本身:库存对账、资金对账、优惠券对账、积分对账——凡是跨存储的最终一致,都是恒等式加流水加修复。对账平台是资损防控的地基:很多资损事故的根因不是某个 bug,而是"没有发现差异的机制",差异存在了三天没人知道。下一篇解决另一个层面的问题:系统真的挂了怎么办——降级与兜底

503

10 年全栈工程师 · 503咖啡馆主理人

#数据对账#最终一致#恒等式#资损防控#差异修复

评论 (0)

相关推荐

连载中 12/20

排查四件套:jstack、jmap、jstat、jcmd 的实战分工

jstack 看线程在干什么,jmap 看堆里装了什么,jstat 看运行时在变什么,jcmd 是统一入口。四把刀各管一段,配合着用没有查不动的现场。

#jstack#jmap#jstat#jcmd#排查工具
2026-09-16 · 4 阅读 · 0 评论 · 0 赞
连载中 11/20

GC 日志:把回收过程翻译成人话

一行 GC 日志里塞着七种信息:谁触发的、收了哪、停了多久、活了哪些。加上 -Xlog 配置,再加上日志分析工具,GC 不再是只能盯监控曲线的黑盒。

#GC日志#Xlog#日志分析#GC监控#Full GC排查
2026-09-16 · 13 阅读 · 0 评论 · 0 赞
连载中 10/20

ZGC:亚毫秒停顿是怎么炼成的

百 G 大堆停顿不到一毫秒,靠的是把搬家全部挪到并发阶段——着色指针让引用自带状态,读屏障让搬运中的对象依然可访问。代价是吞吐与内存,收益是停顿与堆大小解耦。

#ZGC#着色指针#读屏障#亚毫秒停顿#分代ZGC
2026-09-15 · 8 阅读 · 0 评论 · 0 赞