十几个代购客户共用一套系统。某天一个客户说,在列表里看到了别人的订单。排查下来是数据库隔离没做好,单表设计导致数据越界访问。团队连夜重构,改成按租户ID路由分库分表,才把风波按下去。库存数据串户这种事,在海外仓的WMS和ERP对接里会放大。一旦串户,超卖、缺货跟着就来。多租户环境下的数据隔离和一致性,是对接绕不开的第一件事。
多租户库存隔离与路由策略
WMS管物理货架,ERP管逻辑账本。两边对接,库存数据在物理和逻辑上都得隔离。我们用的是共享数据库加分库分表,路由键拿租户ID和仓库ID做组合哈希。这样能避开单点瓶颈,跨租户查询也不容易越界。物理仓网和逻辑租户的映射,要放进配置中心管起来。ERP每次来查库存或同步,先到配置中心拿路由规则,请求才会落到对的分片。数据访问层代码里强制带租户上下文,没带的查询会被拦截器直接拒掉。这样硬性拦一道,数据串味基本没了。
大促期间的热点商品,单个分片容易成为瓶颈。库存分片技术就是把同一商品的库存拆到多个Redis节点或数据库行里。扣减时随机选一个分片,不够则降级到其他分片。并发压力被摊开,整体吞吐量也就上去了。
状态机流转与预扣减机制
ERP下发出库指令,WMS负责拣实物货。两个系统之间隔着网络延迟和处理时间差。高并发下直接去关系型数据库里扣库存,很容易超卖。所以库存预扣减一般放在Redis里做,Lua脚本把扣减操作弄成原子的。像 Taocarts 这种做跨境代购和集运的独立站系统,多仓多租户合包发货时,也得靠预扣减来维持前端展示和物理库存一致。预扣减成功之后,WMS异步往消息队列丢一条消息,消费端再更新ERP的逻辑库存。
WMS内部得维护一套状态机:待拣货、拣货中、已出库,每次流转都记日志。状态机和扣减动作绑在一起,防止重复扣。订单取消或支付超时,锁定的库存要释放,释放也得通过Lua脚本在Redis里原子执行:冻结库存加回可用,同时清掉冻结记录。状态机里加一个已取消,和释放动作强绑定,不然业务状态和库存账会各说各话。
-- WMS与ERP对接时的Redis库存预扣减Lua脚本
-- KEYS[1] 库存key, ARGV[1] 扣减数量
local stock = tonumber(redis.call('get', KEYS[1]))
if stock == nil then
return -1
end
local deduct_num = tonumber(ARGV[1])
if stock >= deduct_num then
redis.call('decrby', KEYS[1], deduct_num)
return stock - deduct_num
else
return -2
end
异步补偿与最终一致性
网络抖动,或者WMS宕机,都可能出现预扣减成功但ERP没同步的情况。异步补偿机制得跟上。消息队列自带重试,可以设置成阶梯式策略;连续失败超过阈值的消息丢进死信队列,人工或定时任务再处理。每天凌晨跑一次全量对账,把WMS物理库存和ERP逻辑库存拉出来对比,生成差异报表。差异数据自动触发补偿消息,两边账本被改写。引入对账机制后,库存差异率从百分之二降到了万分之三。分布式系统没法保证强一致性,最终一致性是性能和准确之间比较现实的折衷。补偿还得讲幂等,ERP端收到扣减消息时用唯一流水号做防重,同一笔变动不能被处理两遍。
系统架构的演进总是这样:解决着旧问题,又撞上新的。库存对接没有一劳永逸的答案,隔离做严谨,补偿做可靠,业务才撑得住。想了解这类海外好物怎么买,可以关注我们。