唉,因為一個疏忽超賣了 100 瓶飛天茅臺!
點擊上方藍色字體,選擇“置頂或者星標”?
優(yōu)質文章第一時間送達!
事故現(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請求用戶服務進行用戶相關的校驗
????????????// 用戶活動校驗
????????????
????????????// 庫存校驗
????????????Object?stock = redisTemplate.opsForHash().get(key+":info", "stock");
????????????assert stock != null;
????????????if?(Integer.parseInt(stock.toString()) <= 0) {
????????????????// 業(yè)務異常
????????????} else?{
????????????????redisTemplate.opsForHash().increment(key+":info", "stock", -1);
????????????????// 生成訂單
????????????????// 發(fā)布訂單創(chuàng)建成功事件
????????????????// 構建響應VO
????????????}
????????}
????} finally?{
????????// 釋放鎖
????????stringRedisTemplate.delete("key");
????????// 構建響應VO
????}
????return?response;
}
事故原因
事故分析
沒有其他系統(tǒ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會返回操作之后的結果,這個過程是原子性的
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) {
????????????// 業(yè)務異常
????????}
????????// 用戶活動校驗
????????// 庫存校驗,基于redis本身的原子性來保證
????????Long currStock = stringRedisTemplate.opsForHash().increment(key + ":info", "stock", -1);
????????if?(currStock < 0) { // 說明庫存已經(jīng)扣減完了。
????????????// 業(yè)務異常。
????????????log.error("[搶購下單] 無庫存");
????????} else?{
????????????// 生成訂單
????????????// 發(fā)布訂單創(chuàng)建成功事件
????????????// 構建響應
????????}
????} finally?{
????????distributedLocker.safedUnLock(key, val);
????????// 構建響應
????}
????return?response;
}
深度思考
分布式鎖有必要么
分布式鎖的選型
再次思考分布式鎖有必要么
// 通過消息提前初始化好,借助ConcurrentHashMap實現(xiàn)高效線程安全
private?static?ConcurrentHashMapSECKILL_FLAG_MAP = new?ConcurrentHashMap<>();
// 通過消息提前設置好。由于AtomicInteger本身具備原子性,因此這里可以直接使用HashMap
private?static?MapSECKILL_STOCK_MAP = new?HashMap<>();
...
public?SeckillActivityRequestVO seckillHandle(SeckillActivityRequestVO request) {
SeckillActivityRequestVO response;
????Long seckillId = request.getSeckillId();
????if(!SECKILL_FLAG_MAP.get(requestseckillId)) {
????????// 業(yè)務異常
????}
?????// 用戶活動校驗
?????// 庫存校驗
????if(SECKILL_STOCK_MAP.get(seckillId).decrementAndGet() < 0) {
????????SECKILL_FLAG_MAP.put(seckillId, false);
????????// 業(yè)務異常
????}
????// 生成訂單
????// 發(fā)布訂單創(chuàng)建成功事件
????// 構建響應
????return?response;
}
總結
更多推薦內(nèi)容
↓↓↓
如果你喜歡本文
請長按二維碼,關注公眾號
轉發(fā)朋友圈,是對我最大的支持喲
以上,便是今天的分享,希望大家喜歡,覺得內(nèi)容不錯的,歡迎「分享」「贊」或者點擊「在看」支持,謝謝各位。
評論
圖片
表情

