分布式鎖使用不當(dāng),釀成重大事故!
點擊上方?泥瓦匠 關(guān)注我!
來源 :https://urlify.cn/MVBvmy
# 事故現(xiàn)場
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請求用戶服務(wù)進行用戶相關(guān)的校驗// 用戶活動校驗// 庫存校驗Object stock = redisTemplate.opsForHash().get(key+":info", "stock");assert stock != null;if (Integer.parseInt(stock.toString()) <= 0) {// 業(yè)務(wù)異常} else {redisTemplate.opsForHash().increment(key+":info", "stock", -1);// 生成訂單// 發(fā)布訂單創(chuàng)建成功事件// 構(gòu)建響應(yīng)VO}}} finally {// 釋放鎖stringRedisTemplate.delete("key");// 構(gòu)建響應(yīng)VO}return response;}
# 事故原因
# 事故分析
沒有其他系統(tǒng)風(fēng)險容錯處理
看似安全的分布式鎖其實一點都不安全
非原子性的庫存校驗
# 解決方案
實現(xiàn)相對安全的分布式鎖
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));}
實現(xiàn)安全的庫存校驗
// redis會返回操作之后的結(jié)果,這個過程是原子性的Long?currStock?=?redisTemplate.opsForHash().increment("key",?"stock",?-1);
發(fā)現(xiàn)沒有,代碼中的庫存校驗完全是“畫蛇添足”。
改進之后的代碼
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) {// 業(yè)務(wù)異常}// 用戶活動校驗// 庫存校驗,基于redis本身的原子性來保證Long currStock = stringRedisTemplate.opsForHash().increment(key + ":info", "stock", -1);if (currStock < 0) { // 說明庫存已經(jīng)扣減完了。// 業(yè)務(wù)異常。log.error("[搶購下單] 無庫存");} else {// 生成訂單// 發(fā)布訂單創(chuàng)建成功事件// 構(gòu)建響應(yīng)}} finally {distributedLocker.safedUnLock(key, val);// 構(gòu)建響應(yīng)}return response;}
# 深度思考
分布式鎖有必要么
分布式鎖的選型
再次思考分布式鎖有必要么
// 通過消息提前初始化好,借助ConcurrentHashMap實現(xiàn)高效線程安全private static ConcurrentHashMapSECKILL_FLAG_MAP = new ConcurrentHashMap<>(); // 通過消息提前設(shè)置好。由于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)) {// 業(yè)務(wù)異常}// 用戶活動校驗// 庫存校驗if(SECKILL_STOCK_MAP.get(seckillId).decrementAndGet() < 0) {SECKILL_FLAG_MAP.put(seckillId, false);// 業(yè)務(wù)異常}// 生成訂單// 發(fā)布訂單創(chuàng)建成功事件// 構(gòu)建響應(yīng)return response;}
# 總結(jié)

往期推薦
下方二維碼關(guān)注我

技術(shù)草根,堅持分享?編程,算法,架構(gòu)?等干貨和思考~
評論
圖片
表情
