国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

【開發(fā)經(jīng)驗】之記一次Redis分布式鎖造成的事故

這篇具有很好參考價值的文章主要介紹了【開發(fā)經(jīng)驗】之記一次Redis分布式鎖造成的事故。希望對大家有所幫助。如果存在錯誤或未考慮完全的地方,請大家不吝賜教,您也可以點擊"舉報違法"按鈕提交疑問。

一、事故場景

有次,運營和商家做了個限量搶購活動,限量100件,但活動當天卻超賣了,最終賣出的數(shù)量是160多件。這種超賣是比較嚴重的事故,出現(xiàn)了的話基本上和分布式鎖有關(guān)系。

二、問題分析

項目中的搶購訂單使用了分布式鎖,而分布式鎖的是基于Redis實現(xiàn)的,下面是訂單搶購核心代碼(使用偽代碼講解):

String key = "key:" + request.getSeckillId;
Boolean lockFlag = null;
try {
	// 獲取分布式鎖
    Boolean lockFlag = redisTemplate.opsForValue().setIfAbsent(key, "val", 10, TimeUnit.SECONDS);
    if (lockFlag) {
        // HTTP請求調(diào)用其他服務接口
        ......
        // 庫存校驗
        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);
            // 發(fā)送事件,異步生成訂單
        }
    }
} finally {
    // 釋放鎖
    if (lockFlag) {
    	stringRedisTemplate.delete("key");
    }
}

代碼中給分布式鎖設(shè)置10秒超時時間來保障業(yè)務邏輯有足夠的執(zhí)行時間,并且也對庫存進行了校驗,整塊邏輯采用 try-finally 語句塊來保證鎖一定會及時釋放,代碼看起來很安全,平時也沒有出現(xiàn)問題。

但問題就在于,中間有調(diào)用其他服務接口,并且在搶購活動開始的一瞬間,因為流量過大,導致調(diào)用所依賴的服務超時而鎖失效。這個時候就會發(fā)生一連串的連鎖反應:一開始獲得鎖的線程還沒有執(zhí)行完畢,鎖就被另一個線程獲取了,而第一個線程執(zhí)行業(yè)務邏輯完畢后執(zhí)行釋放鎖的操作時就會把第二個線程的鎖給釋放了,然后第三個線程再次獲取鎖,就這樣陷入了惡性循環(huán)。

當然,雖然鎖失去了作用,但還有個庫存校驗邏輯,但是偏偏庫存校驗邏輯不是非原子性的,代碼中庫存校驗方式是先從 Redis 中 get 出庫存數(shù)量,然后判斷庫存是否還有,最后再進行庫存的扣減。這種庫存校驗的方式在鎖正常的情況下也是可以的,但一旦鎖失效就是不安全了。

所以,問題的根本原因在于庫存校驗嚴重依賴了分布式鎖最終才導致超賣。

三、解決問題

從上面的分析可以知道,問題就出現(xiàn)在分布式鎖和庫存校驗那里,所以,我們可以對癥下藥。

1、使用相對安全的分布式鎖

相對安全的定義就是:加鎖和解鎖必須是同一個客戶端,客戶端自己不能把別人加的鎖給解了。但即使是這樣也無法保障業(yè)務的絕對安全,因為鎖的過期時間始終是有界的,除非不設(shè)置過期時間或者把過期時間設(shè)置的很長,但這樣做也會帶來其他問題,沒有意義。

redisTemplate.opsForValue().setIfAbsent() 就是對應 redis 的命令 set key value [EX seconds] [PX milliseconds] [NX|XX],這是安全的同時也是原子性的。所以我們只需要實現(xiàn)安全的釋放鎖即可。

要想實現(xiàn)相對安全的釋放分布式鎖,必須依賴 key 的 value 值。在釋放鎖的時候,通過 value 值的唯一性來保證不會勿刪。我們基于 LUA 腳本實現(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'";
    RedisScript<String> redisScript = RedisScript.of(luaScript);
    redisTemplate.execute(redisScript, Collections.singletonList(key), Collections.singleton(val));
}

2、實現(xiàn)安全的庫存校驗

如果我們對于并發(fā)有比較深入的了解的話,會發(fā)現(xiàn)想 Redis 的 get and compare/ read and save 等操作,都是非原子性的。如果要實現(xiàn)原子性,我們可以借助 LUA 腳本來實現(xiàn)。但就我們這個例子中,由于搶購活動一次只能購買一件,所以可以不用基于LUA腳本實現(xiàn)而是基于 redis 本身的原子性:

// redis 操作完數(shù)據(jù)并返回操作結(jié)果的整個過程是原子性的
Long currStock = redisTemplate.opsForHash().increment("key", "stock", -1);

所以,代碼中的庫存校驗是多余的,下面是優(yōu)化后結(jié)果:

String key = "key:" + request.getSeckillId();
String val = UUID.randomUUID().toString();
try {
	// 獲取分布式鎖
    Boolean lockFlag = redisTemplate.opsForValue().setIfAbsent(key, val, 10, TimeUnit.SECONDS);
    if (!lockFlag) {
        // 業(yè)務異常
    }
    // HTTP請求調(diào)用其他服務接口
    ......
    // 庫存校驗,基于redis本身的原子性來保證
    Long currStock = stringRedisTemplate.opsForHash().increment(key + ":info", "stock", -1);
    if (currStock < 0) { // 說明庫存已經(jīng)扣減完了。
        // 業(yè)務異常。
        log.error("[搶購下單] 無庫存");
    } else {
        // 發(fā)送事件,異步生成訂單
    }
} finally {
    safedUnLock(key, val);
}

四、方案優(yōu)化

1、是否需要分布式鎖

其實可以發(fā)現(xiàn),我們借助于redis本身的原子性扣減庫存,也是可以保證不會超賣的。對的。但是如果沒有這一層鎖的話,那么所有請求進來都會走一遍業(yè)務邏輯,由于依賴了其他系統(tǒng),此時就會造成對其他系統(tǒng)的壓力增大。這會增加的性能損耗和服務不穩(wěn)定性,得不償失。

2、分布式鎖的選型

  • 可以使用 Redission 來解決鎖的存續(xù)問題;
  • 也可以用 RedLock 來實現(xiàn)分布式鎖。RedLock的可靠性更高,但其代價是犧牲一定的性能。在本場景,這點可靠性的提升遠不如性能的提升帶來的性價比高。如果對于可靠性極高要求的場景,則可以采用 RedLock 來實現(xiàn)。

3、能否用數(shù)據(jù)庫做最終的防護

如果在 Redis 中扣減庫存成功后進行數(shù)據(jù)庫的同步操作,比如使用 set stock = stock - 1 where stock - 1 來保證不會超賣,將這做為最后的保障手段。但在高并發(fā)場景下操作數(shù)據(jù)庫更新的話會有性能損耗,也會給數(shù)據(jù)庫帶來很大壓力,但要論證多大才算大,以我的經(jīng)驗 mysql 簡單字段的并發(fā)寫 1000~2000 qps是完全扛得住的,這需要壓測來論證,當然如果并發(fā)太高也可以只使用緩存操作,異步機制同步

在性能要求極高的場景下,一般數(shù)據(jù)以緩存為準,支付交易等也是如此,分布式場景下,大多數(shù)場景都是最終一致性。文章來源地址http://www.zghlxwxcb.cn/news/detail-481403.html

到了這里,關(guān)于【開發(fā)經(jīng)驗】之記一次Redis分布式鎖造成的事故的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來自互聯(lián)網(wǎng)用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權(quán),不承擔相關(guān)法律責任。如若轉(zhuǎn)載,請注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實不符,請點擊違法舉報進行投訴反饋,一經(jīng)查實,立即刪除!

領(lǐng)支付寶紅包贊助服務器費用

相關(guān)文章

  • zookeeper實現(xiàn)分布式鎖-curator,java面試項目經(jīng)驗案例

    zookeeper實現(xiàn)分布式鎖-curator,java面試項目經(jīng)驗案例

    //5.如果不是第一個節(jié)點,需要監(jiān)聽前一個節(jié)點 //用一個臨時變量記錄當前節(jié)點的上一個節(jié)點 String previousNode = firstNode; for(String node : children){ if(currentNode.endsWith(node)){ //如果當前節(jié)點是node節(jié)點 ,那么就監(jiān)聽它的上一個節(jié)點 :比如 currentNode 這里是 0003節(jié)點 ,那 node就是 0002節(jié)點

    2024年04月23日
    瀏覽(50)
  • Redis與分布式-分布式鎖

    Redis與分布式-分布式鎖

    接上文 Redis與分布式-集群搭建 為了解決上述問題,可以利用分布式鎖來實現(xiàn)。 重新復制一份redis,配置文件都是剛下載時候的不用更改,然后啟動redis服務和redis客戶。 redis存在這樣的命令:和set命令差不多,但是它有一個機制,當指定的key不存在的時候,才能進行插入,實

    2024年02月07日
    瀏覽(27)
  • Redis分布式鎖和分布式事務

    Redis分布式鎖和分布式事務 一、Redis分布式鎖 1.1 watch和事務實現(xiàn)分布式鎖 原理是通過watch來觀察一個變量,一個線程在操作的時候,其他線程會操作失敗,相當于樂觀鎖。 1.2 setnx實現(xiàn)分布式鎖 原理是通過setnx設(shè)置一個變量,設(shè)置成功的線程搶到鎖,執(zhí)行相關(guān)的業(yè)務,執(zhí)行完畢

    2024年02月09日
    瀏覽(25)
  • 【redis】redis分布式鎖

    一、為什么需要分布式鎖 1.在java單機服務中,jvm內(nèi)部有一個全局的鎖監(jiān)視器,只有一個線程能獲取到鎖,可以實現(xiàn)線程之間的互斥 2.當有多個java服務時,會有多個jvm,也會有多個鎖監(jiān)視器,這樣沒辦法使得多個jvm之間的線程互斥,所以無法使用jvm內(nèi)部的鎖監(jiān)視器,也就是s

    2023年04月25日
    瀏覽(28)
  • 分布式鎖實現(xiàn)(mysql,以及redis)以及分布式的概念

    分布式鎖實現(xiàn)(mysql,以及redis)以及分布式的概念

    我旁邊的一位老哥跟我說,你知道分布式是是用來干什么的嘛?一句話給我干懵了,我能隱含知道,大概是用來做分壓處理的,并增加系統(tǒng)穩(wěn)定性的。但是具體如何,我卻道不出個1,2,3?,F(xiàn)在就將這些做一個詳細的總結(jié)。至少以后碰到面試官可以說上個123。 那么就正式進入

    2024年01月21日
    瀏覽(37)
  • 分布式系統(tǒng)面試全集通第一篇(dubbo+redis+zookeeper----分布式+CAP+BASE+分布式事務+分布式鎖)

    分布式系統(tǒng)面試全集通第一篇(dubbo+redis+zookeeper----分布式+CAP+BASE+分布式事務+分布式鎖)

    什么是分布式 一個系統(tǒng)各組件分別部署在不同服務器。彼此通過網(wǎng)絡(luò)通信和協(xié)調(diào)的系統(tǒng)。 也可以指多個不同組件分布在網(wǎng)絡(luò)上互相協(xié)作,比如說電商網(wǎng)站 也可以一個組件的多個副本組成集群,互相協(xié)作如同一個組件,比如數(shù)據(jù)存儲服務中為了數(shù)據(jù)不丟失而采取的多個服務備

    2024年04月11日
    瀏覽(29)
  • Redis 分布式緩存

    Redis 分布式緩存

    單點 Redis 的問題及解決 數(shù)據(jù)丟失:實現(xiàn)Redis數(shù)據(jù)持久化 并發(fā)能力:搭建主從集群,實現(xiàn)讀寫分離 存儲能力:搭建分片集群,利用插槽機制實現(xiàn)動態(tài)擴容 故障恢復能力:利用哨兵機制,實現(xiàn)健康檢測和自動恢復 RDB RDB全稱Redis Database Backup file (Redis數(shù)據(jù)備份文件),也被叫做

    2024年02月10日
    瀏覽(24)
  • Redis分布式緩存

    Redis分布式緩存

    -- 基于Redis集群解決單機Redis存在的問題 單機的Redis存在四大問題: Redis有兩種持久化方案: RDB持久化 AOF持久化 ? ? ? ?RDB全稱Redis Database Backup file(Redis數(shù)據(jù)備份文件),也被叫做 Redis數(shù)據(jù)快照 。簡單來說就是把 內(nèi)存中的所有數(shù)據(jù)都記錄到磁盤 中。當Redis實例故障重啟后,

    2024年02月12日
    瀏覽(19)
  • Redis+分布式+秒殺

    Redis+分布式+秒殺

    關(guān)于mysql關(guān)系型數(shù)據(jù)庫的一些分析: 1、從性能上:如果查詢結(jié)果不是很頻繁變動的SQL語句,我們就沒有必要每次都去查詢數(shù)據(jù)庫,可以把這種數(shù)據(jù)放在基于緩存的數(shù)據(jù)庫中,這樣不僅提升了查詢效率還分擔了數(shù)據(jù)庫壓力。 2、從并發(fā)上:在大并發(fā)的情況下(比如618秒殺活動,

    2024年02月06日
    瀏覽(19)
  • redis與分布式

    redis與分布式

    主從復制,是指將一臺Redis服務器的數(shù)據(jù),復制到其他的Redis服務器。前者稱為主節(jié)點(Master),后者稱為從節(jié)點(Slave),數(shù)據(jù)的復制是單向的,只能由主節(jié)點到從節(jié)點。Master以寫為主,Slave 以讀為主(只讀模式),當主節(jié)點關(guān)閉后,從節(jié)點依然可以讀取數(shù)據(jù),但是會報錯 優(yōu)點 :

    2024年02月12日
    瀏覽(25)

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包