日期:2026-07-15
某生鲜平台在扩展即时零售业务时,面临一个典型的"线上线下两张皮"问题。线下分布在城市各处的门店与前置仓维护着自己的库存台账,而线上商城(基于我们采用的 XpShop 即时零售解决方案)在高峰期每秒都会产生大量下单请求。由于库存数据以分钟级定时同步,线上看到的"有货"在真实仓里可能早已被线下 POS 走掉,反之亦然。结果是超卖(用户下单成功却无货可发)与缺货(仓里有货却显示售罄)同时出现,客服投诉和履约违约同步攀升。我们介入时,对方最关心的不是功能多炫,而是"能不能先把库存这口气稳住"。
这个项目还有一个容易被低估的复杂度:生鲜品本身有保质期与批次,临期商品要优先出库,滞销要快速调价,这要求库存不仅"准",还要"活"——能跟着促销、损耗、盘点实时变化。如果库存同步是 T+1 的批量任务,那么上午卖爆的叶菜到了下午其实已经补不进来,系统却还在接单,超卖几乎必然。我们当时给客户的第一条建议就是:别急着加功能,先把库存链路的时延从分钟级压到秒级以内。
这个项目里我们落地了三块核心链路:
第一是前置仓库存实时同步。每个前置仓与门店的库存变更(入库、线下销售、盘点调拨、损耗报损)通过消息总线实时上报,中心库存服务做聚合,线上下单时读取的是"实时可用量"而非昨天的快照。我们为每种库存变更定义了统一事件结构,避免门店 ERP、POS、WMS 各说各话。
第二是线上下单、门店/前置仓履约。用户在小程序下单后,系统根据收货地址做一次就近履约路由,把订单派给覆盖范围内时效最优的仓,仓内拣货、打包、由平台骑手或第三方运力送出。拣货环节还对接了店员的代客下单与手持终端,确保线下走货能立刻回写库存。
第三是到店核销。针对预售卡券、到店自提、社区团购等场景,用户到店出示核销码,店员扫码完成履约闭环,核销记录直接进入对账流水,与订单、卡券、支付三方勾稽。
库存实时同步与超卖防护是最大难点。定时批量同步在秒杀场景下必然超卖,我们改为"预扣 + 异步回写"模型:下单瞬间先在 Redis 原子扣减可用量,再异步落库并广播库存事件。预扣失败直接拦截下单,从源头杜绝超卖;若支付超时未确认,则通过延时消息把预扣量回滚。核心伪代码如下:
# 库存预扣(原子操作)
def try_deduct(sku, wh_code, qty):
key = f"stock:{sku}:{wh_code}"
# Lua 保证扣减与判负原子
script = """
local cur = tonumber(redis.call('GET', KEYS[1]) or '0')
if cur < tonumber(ARGV[1]) then return -1 end
redis.call('DECRBY', KEYS[1], ARGV[1])
return 1
"""
return redis.eval(script, [key], [qty])
# 支付超时回滚
def rollback_on_timeout(sku, wh_code, qty, order_id):
redis.incrby(f"stock:{sku}:{wh_code}", qty)
db.execute("UPDATE stock SET locked=locked-? WHERE sku=? AND wh=?",
qty, sku, wh_code)为了扛住热点 SKU 的并发,我们进一步把库存 key 按仓加 SKU 做本地分片,预扣请求在分片内串行、跨分片并行,配合 Redis 单线程 Lua 的原子性,既保一致又不损失吞吐。压测时我们发现,纯靠单 key 扣减在大促热点上会成为瓶颈,分片后热点被摊到多个物理槽位,P99 扣减时延稳定在毫秒级。
案例片段(已脱敏):某生鲜平台大促期间,单仓热点 SKU 的预扣并发峰值约 1.2 万次/秒,借助 Redis 单线程 Lua 扣减与本地库存分片,超卖事故数由改造前的月均 7 起降至 0。
履约路由需要"找最近且能发的仓"。我们维护了一份仓的经纬度与实时负荷表,用地理围栏加负载权重做打分排序:优先选配送半径内库存充足、当前负荷未触顶的仓;当首选仓缺货时,自动升级到次近仓并提示时效变化,避免硬失败。路由还叠加了"生鲜优先就近"策略,叶菜类优先 30 分钟可达的仓,耐储品可放宽到次近仓以平衡全局负荷。
-- 履约路由候选仓打分(示意)
SELECT wh_code,
geo_distance(user_lng, user_lat, wh_lng, wh_lat) AS dist,
available_qty,
CASE WHEN load_rate < 0.85 THEN 1 ELSE 0 END AS healthy
FROM warehouse
WHERE available_qty >= :need
AND geo_distance(user_lng, user_lat, wh_lng, wh_lat) < :radius
ORDER BY (dist * 1.0 + load_rate * 500) ASC
LIMIT 3;核销防刷与对账则不能只靠一个核销码。我们给每个核销码加上一次性令牌、有效期与门店绑定,核销时服务端校验"码-店-时"三元组,重复核销直接拒绝。所有核销事件流入对账流水表,T+1 与订单、卡券系统做三方勾稽,异常自动告警。我们还对核销做了限频与风控评分,异常设备批量核销会被临时冻结,防止脚本批量薅券。
案例片段(已脱敏):某生鲜平台到店核销上线后,在一次营销活动中捕获到约 230 次重复核销尝试,系统因"码-店-时"绑定全部拦截,核销异常率由改造前的约 1.8% 降至 0.2% 以下。
| 指标 | 改造前 | 改造后 | 说明 |
|---|---|---|---|
| 缺货率 | 约 6.5% | 约 1.2% | 实时可用量替代快照,缺货误判大幅下降 |
| 超卖事故数 | 月均 7 起 | 0 起 | 预扣模型从源头拦截 |
| 平均履约时效 | 约 58 分钟 | 约 32 分钟 | 就近路由 + 负荷均衡 |
| 核销异常率 | 约 1.8% | 0.2% 以下 | 三元组校验 + 对账勾稽 |
需要说明,表中为脱敏示意值,用于刻画趋势量级,不作为审计口径。上线后我们还持续观察了约一个季度,超卖事故在高峰期仍保持为零,说明预扣模型在真实流量下是稳健的。
库存是履约的命根,预扣优于事后补偿。我们在多个即时零售项目里反复验证:与其下单成功后再去抢库存、抢不到就退款赔券,不如在下单入口就把库存"占住",把冲突消灭在链路最前端。其次是链路要分层解耦——库存、路由、核销各管一段,出问题时能独立定位与灰度,而不是一团乱麻。生鲜场景还要额外关注库存的"活性",把损耗、临期、调价也纳入实时事件,否则再准的快照也会过时。最后是核销这类看似简单的环节,必须带状态与对账,否则营销一放量就是资损。这套"预扣 + 路由 + 闭环核销"的组合打法,后续在多个同城零售项目中被直接复用。