分布式锁的重要性
前言
我们公司的库存业务比较特殊,修改库存状态和数量有多个入口比如有自己的商家管理中心、开放平台接口等。还可以将库存同步到其它系统去其它系统根据我们的同步的状态进行展示或者不展示。暂且管我们库存中心为A,其他系统为B
排查过程
【验证】
查询库存存储的:不可售
【先解决商家问题】
先通知运营再次修改下商品库存状态,修改完成后发现已经不展示商品了(此时心理在想大概率是同步出的问题)
【排除问题】
1.查询库存状态同步流水,发现A同步流水没问题而且都成功了。但是发现有点特殊状况同一时刻同步了2次是库存的状态
| sku | 同步状态 | 时间 |
|---|---|---|
| 111 | 不可售 | 2021-3-30 22:44:21 |
| 111 | 可售 | 2021-3-30 22:44:21 |
2.查询B系统的同步流水,发现也是同一时刻有2次更新状态但是与我们的刚好顺序是相反的
| sku | 同步状态 | 时间 |
|---|---|---|
| 111 | 可售 | 2021-3-30 22:44:21 |
| 111 | 不可售 | 2021-3-30 22:44:21 |
3.此时就觉得是并发问题导致的最终状态不一致于是查询代码 【伪代码】
try {
//查询A系统商品的售卖状态
getStockStatus();
//TODO其它特殊业务代码
//将A系统步库存状态同步给B系统
updateMainStockStatus();
} catch (Exception ex) {
//其他
}
【最终问题】
查询状态和同步逻辑不在同一个线程内在操作。
下面有个示意图:
【解决方案】
加上了Redis分布式锁 【伪代码】为了防止这种问题再次发生还增加了同步结果对比功能和查询同步结果对比工具等增加了系统的稳定可靠。
分布式锁:原理是就是多台机器,去争抢一个资源,谁争抢成功,那么谁就持有了这把锁,然后去执行后续的业务逻辑,执行完毕后,把锁释放掉。保证只有一个在进行工作
//业务代码key
String statusLockKey = "lockKey";
lockUtil.doExecuteLock(statusLockKey, new LockUtil.AbstractDone() {
@Override
public Object execute() throws Exception {
//查询A系统商品的售卖状态
getStockStatus();
//TODO其它特殊业务代码
//将A系统步库存状态同步给B系统
updateMainStockStatus();
return null;
}
});
我的写法主要是靠redis的setnx和lua脚本实现,现在大家可以不用自己写分布式锁,redission有现成的分布式锁以及已经给大家封装好了。下次可以跟大家聊聊redisson工具
总结
- 特别是高并发系统一点要注意系统的最终结果一致性
- 解决并发问题通常可以有分布式锁或者顺序处理(顺序处理key使用版本号等)
- 做好系统监控和业务监控
扫下试试以后会有惊喜试试看呀