队列同步 - 跨平台接口调用及值同步处理
年前遇到这样一种情况,记录一下,以防后续出现同样的错误,影响后续的处理及兼容。
场景如下:
有几个限制条件:
1. 需求指明不可以对店铺分库存,因为无法确认店铺售卖哪个卖的多,哪个少;
2. 调用三方平台API新增商品时,SKU量、库存量、运费模板都有限制。库存量不可超过1W;
3. 三方平台商品存在几种状态:待审核、审核待修改、审核通过(自动为上架)、审核拒绝、下架、上架、删除等;
4. 三方平台API操作商品库存信息,仅且上架才可修改库存,其余状态均异常【重点考虑】
5. 因自身平台商品,存在对应多平台情况,需要考虑不同平台商品的库存一致性处理,避免超卖【重点考虑:试想一下能否绝对避免】;
方案
前因:场景梳理
1. 由限制条件2得出,库存超过需要额外同步;
2. 下单库存变更,存在下单扣减、取消支付(下单)回增;
3. 运营后台增减库存;
4. 运营手动更改三方店铺库存;
大略:
1. 实时同步
a. 以三方最高库存值进行同步,多余部分队列补偿;
b. 商品同步时,库存全部为零,待三方店铺商品上架后,在同步库存;
2. 定时补偿
a. 定时任务补偿所有商品的店铺库存值,进行全量覆盖的操作,避免直接操作三方库存的情况发生;
详细:
每次库存变更,使用队列(Queue、redis ...)等方式,将变更增量加入队列,增量值采用加锁方式,保证库存变更最终值的准确性;
如:可以使用redis中的set结构,对 [ 三方店铺-商品ID-SKUID ] 进行缓存变更信息,再存储对应的sku增量值
① 第一种方式::
如图,使用单线程从队列中 pop 除元素,进行执行处理,失败则再次添加在队中。
思考:若①方案中,某个数据执行异常,则一直在存取之间处理,那么这种方式就会一直耗费CPU资源,如何解决这种处理呢?
② 第二种方式:
分两类型线程执行:
i:分发任务,将set中的member进行分发至队列 ( Queue ),判断当前被分发的队列是否为空且当前元素可以被操作,否需要睡眠N秒,是则继续分发,注意线程执行幂等;
ii:执行任务,从队列中poll / pop出元素,获取并移除 [ 原子操作、加锁保证或redis-lua脚本 ] 增量值,调用三方库存变更接口,失败了,判断原因,若为三方库存少于扣减值,则清零,否则缓存1H ( 不可操作标识 ),并再次添加队列中,等待1H后的再次触发;
③ 第三种方式:
思考 !!!
后续调整
队列调整 ? 方案调优 ? 超卖绝对避免 ?
1. 库存分发时,注意幂等性;
2. 分发执行时,避免死循环造成的CPU,需测试该操作对性能的影响,避免线上CPU爆满的情况发生;
3. 可以合理利用Thread.sleep(0L); 或者线程礼让,避免CPU一直被占用的情况发生,从而导致CPU爆满;
4. 可以使用redis - hash结构替换,hash中也存在increment的操作,也可以保障库存增量变更时的值的最终一致;
使用 redis-set + string 可以避免对同一SKU多次变更操作; hash结构同理
5. 活动前能否将涉及所有商品统一处理,减少异常情况发生;
6. 记录库存的变更流水,便于后续核对;
7. 除上述方式外,是否还有其他实现的可能,并且能够避免超卖的风险?
whatever is worth doing is worth doing well --- fn