分布式锁使用不当,酿成重大事故!
点击上方 泥瓦匠 关注我!
来源 :https://urlify.cn/MVBvmy
# 事故现场
public SeckillActivityRequestVO seckillHandle(SeckillActivityRequestVO request) {SeckillActivityRequestVO response;String key = "key:" + request.getSeckillId;try {Boolean lockFlag = redisTemplate.opsForValue().setIfAbsent(key, "val", 10, TimeUnit.SECONDS);if (lockFlag) {// HTTP请求用户服务进行用户相关的校验// 用户活动校验// 库存校验Object stock = redisTemplate.opsForHash().get(key+":info", "stock");assert stock != null;if (Integer.parseInt(stock.toString()) <= 0) {// 业务异常} else {redisTemplate.opsForHash().increment(key+":info", "stock", -1);// 生成订单// 发布订单创建成功事件// 构建响应VO}}} finally {// 释放锁stringRedisTemplate.delete("key");// 构建响应VO}return response;}
# 事故原因
# 事故分析
没有其他系统风险容错处理
看似安全的分布式锁其实一点都不安全
非原子性的库存校验
# 解决方案
实现相对安全的分布式锁
public void safedUnLock(String key, String val) {String luaScript = "local in = ARGV[1] local curr=redis.call('get', KEYS[1]) if in==curr then redis.call('del', KEYS[1]) end return 'OK'"";RedisScriptredisScript = RedisScript.of(luaScript); redisTemplate.execute(redisScript, Collections.singletonList(key), Collections.singleton(val));}
实现安全的库存校验
// redis会返回操作之后的结果,这个过程是原子性的Long currStock = redisTemplate.opsForHash().increment("key", "stock", -1);
发现没有,代码中的库存校验完全是“画蛇添足”。
改进之后的代码
public SeckillActivityRequestVO seckillHandle(SeckillActivityRequestVO request) {SeckillActivityRequestVO response;String key = "key:" + request.getSeckillId();String val = UUID.randomUUID().toString();try {Boolean lockFlag = distributedLocker.lock(key, val, 10, TimeUnit.SECONDS);if (!lockFlag) {// 业务异常}// 用户活动校验// 库存校验,基于redis本身的原子性来保证Long currStock = stringRedisTemplate.opsForHash().increment(key + ":info", "stock", -1);if (currStock < 0) { // 说明库存已经扣减完了。// 业务异常。log.error("[抢购下单] 无库存");} else {// 生成订单// 发布订单创建成功事件// 构建响应}} finally {distributedLocker.safedUnLock(key, val);// 构建响应}return response;}
# 深度思考
分布式锁有必要么
分布式锁的选型
再次思考分布式锁有必要么
// 通过消息提前初始化好,借助ConcurrentHashMap实现高效线程安全private static ConcurrentHashMapSECKILL_FLAG_MAP = new ConcurrentHashMap<>(); // 通过消息提前设置好。由于AtomicInteger本身具备原子性,因此这里可以直接使用HashMapprivate static MapSECKILL_STOCK_MAP = new HashMap<>(); ...public SeckillActivityRequestVO seckillHandle(SeckillActivityRequestVO request) {SeckillActivityRequestVO response;Long seckillId = request.getSeckillId();if(!SECKILL_FLAG_MAP.get(requestseckillId)) {// 业务异常}// 用户活动校验// 库存校验if(SECKILL_STOCK_MAP.get(seckillId).decrementAndGet() < 0) {SECKILL_FLAG_MAP.put(seckillId, false);// 业务异常}// 生成订单// 发布订单创建成功事件// 构建响应return response;}
# 总结

往期推荐
下方二维码关注我

技术草根,坚持分享 编程,算法,架构 等干货和思考~
评论
