欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Redis分布式鎖及安全問題解決

 更新時(shí)間:2024年03月25日 15:47:16   作者:林+夕=夢(mèng)  
在分布式環(huán)境中,遇到搶購(gòu)等訪問共享資源的場(chǎng)景時(shí),需要我們有一種鎖機(jī)制去解決并發(fā)問題,本文主要介紹了Redis分布式鎖及安全問題解決,具有一定的參考價(jià)值,感興趣的可以了解一下

一、為什么需要分布式鎖

單機(jī)鎖: 多個(gè)線程同時(shí)改變一個(gè)變量時(shí),需要對(duì)變量或者代碼塊做同步從而保證串行修改變量.

多機(jī)系統(tǒng): 存在多機(jī)器多請(qǐng)求同時(shí)對(duì)同一個(gè)共享資源進(jìn)行修改,如果不加以限制,將導(dǎo)致數(shù)據(jù)錯(cuò)亂和數(shù)據(jù)不一致性. 比如: 庫(kù)存超賣、抽獎(jiǎng)多發(fā)、券多發(fā)放、訂單重復(fù)提交...

二、常見的分布式鎖

實(shí)現(xiàn)方式

優(yōu)點(diǎn)

缺點(diǎn)

應(yīng)用場(chǎng)景

MySQL數(shù)據(jù)庫(kù)表

易于理解/易于實(shí)現(xiàn)

容易出現(xiàn)單點(diǎn)故障、死鎖性能低/可靠性低

適用于并發(fā)量低、

性能要求低的場(chǎng)景

Redis分布式鎖

性能高/易于實(shí)現(xiàn)可跨集群部署,無(wú)單點(diǎn)故障

鎖失效時(shí)間的控制不穩(wěn)定穩(wěn)定性低于

ZooKeeper

適用于高并發(fā)、高性能場(chǎng)景

ZooKeeper

分布式鎖

無(wú)單點(diǎn)故障/可靠性高不可重入/無(wú)死鎖問題

實(shí)現(xiàn)復(fù)雜性能低于緩存分布式鎖

適用于大部分分布式場(chǎng)景,

除對(duì)性能要求極高的場(chǎng)景

三、 用Redis實(shí)現(xiàn)一個(gè)分布式鎖

3.1 SETNX

SET lock 1 NX
    String buyTicket() {
        Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", "1");
        if (lock) {
            try {
                int stockNum = byTicketMapper.selectStockNum();
                if (stockNum > 0) {
                    //TODO  by ticket process....
                    byTicketMapper.reduceStock();
                    return "SUCCESS";
                }
                return "FAILED";
            }finally {
                redisTemplate.delete("lock");
            }
        }
        return "OOPS...PLEASE TRY LATTER";
    }

Java代碼很容易看出, 假如執(zhí)行了加鎖后程序出現(xiàn)宕機(jī), 執(zhí)行不到finally語(yǔ)句塊里的解鎖, 就出會(huì)有死鎖問題. 為了解決死鎖, 很容易就想到給鎖設(shè)置一個(gè)過期時(shí)間. 

3.2 設(shè)置鎖過期時(shí)間和唯一ID

設(shè)置key時(shí)同時(shí)設(shè)置過期時(shí)間:  

SET lock 1 NX EX 30

Java代碼: 

Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", "1", Duration.ofSeconds(10L));

但這會(huì)導(dǎo)致更嚴(yán)重的錯(cuò)刪鎖問題, 比如某個(gè)線程1加鎖后, 執(zhí)行業(yè)務(wù)邏輯比較慢, 鎖過期自動(dòng)釋放了, 此時(shí)線程2競(jìng)爭(zhēng)加鎖成功, 而線程1執(zhí)行了刪除鎖, 以此類推, 相當(dāng)于鎖失效

改進(jìn): 設(shè)置線程UUID, 并且用lua腳本保證GET和DEL原子性操作, 防止刪錯(cuò)key

String buyTicket() {
        String uuid = UUID.randomUUID().toString();
        Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", uuid, Duration.ofSeconds(10L));
        if (lock) {
            try {
                int stockNum = byTicketMapper.selectStockNum();
                if (stockNum > 0) {
                    //TODO by ticket process....
                    byTicketMapper.reduceStock();
                    return "SUCCESS";
                }
                return "FAILED";
            } finally {
                String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
                this.redisTemplate.execute(new DefaultRedisScript<>(script), Arrays.asList("lock"), uuid);
            }
        }
        return "OOPS...PLEASE TRY LATTER";
    }

看起來好像還不錯(cuò), 但是依然有過期時(shí)間無(wú)法完全匹配實(shí)際需求的問題:

太短 -> 鎖失效無(wú)法保證程序正確處理業(yè)務(wù)

太長(zhǎng) -> 異常流程過度占有鎖導(dǎo)致資源浪費(fèi)

有更好的解決方案嗎? 比如開啟一個(gè)后臺(tái)線程, 定時(shí)檢查主線程是否持有鎖(即未完成操作資源), 給它自動(dòng)延長(zhǎng)鎖過期時(shí)間.  幸運(yùn)的javaer 已經(jīng)Redisson庫(kù)封裝好了這些操作.

3.3  Redisson

看門狗機(jī)制: 加一個(gè)后臺(tái)線程定時(shí)檢查鎖,自動(dòng)續(xù)過期時(shí)間

Java代碼

String buyTicket() {
        RLock lock = redissonClient.getLock("lock");
        try {
            if (lock.tryLock(30,TimeUnit.SECONDS)) {
                int stockNum = byTicketMapper.selectStockNum();
                if (stockNum > 0) {
                    //TODO  by ticket process....
                    byTicketMapper.reduceStock();
                    return "SUCCESS";
                }
                return "FAILED";
            }
        }catch (InterruptedException e){
            log.error("Try Lock Error:{}",e.getMessage());
        }finally {
            lock.unlock();
        }
        return "OOPS...PLEASE TRY LATTER";
    }

對(duì)于單機(jī)版的redis至此已經(jīng)是很好的方案了, 然而現(xiàn)實(shí)中大多數(shù)使用的是集群redis... 

四、 主從同步對(duì)分布式鎖的影響

高并發(fā)場(chǎng)景主從切換鎖失效: 試想一下這樣的場(chǎng)景, 主節(jié)點(diǎn)加鎖成功, 沒有同步到從節(jié)點(diǎn)時(shí)主節(jié)點(diǎn)宕機(jī), 此時(shí)從節(jié)點(diǎn)選舉出新的主節(jié)點(diǎn), 它就丟失了還沒同步的鎖, 此時(shí)其他客戶端向新的主節(jié)點(diǎn)請(qǐng)求加鎖會(huì)成功, 導(dǎo)致沖突.

4.1 Redlock

Redlock 的方案官網(wǎng)解釋: 既然主從架構(gòu)有問題, 那就部署多個(gè)主庫(kù)實(shí)例. 

Redlock整體流程:

  • 客戶端在多個(gè) Redis 實(shí)例上申請(qǐng)加鎖
  • 必須保證大多數(shù)節(jié)點(diǎn)(超過半數(shù))加鎖成功
  • 大多數(shù)節(jié)點(diǎn)加鎖的總耗時(shí),要小于鎖設(shè)置的過期時(shí)間
  • 釋放鎖,要向全部節(jié)點(diǎn)發(fā)起釋放鎖請(qǐng)求

疑問: 

1 ) 假如有3個(gè)客戶端競(jìng)爭(zhēng)同一資源, 向5個(gè)Redis請(qǐng)求獲取鎖, 容易出現(xiàn)沒有獲勝者的情況.

-> redis官方:  多路復(fù)用 以及 沒有獲得過半數(shù)鎖的客戶端盡快釋放鎖

2) 某個(gè)主節(jié)點(diǎn)宕機(jī)時(shí)可能出現(xiàn)鎖安全性問題. 比如: 當(dāng)Redis持久化策略為AOF使用appendfsync=everysec即每秒fsync一次, 故障時(shí)會(huì)丟失1秒的數(shù)據(jù), 也就是丟鎖. 當(dāng)該節(jié)點(diǎn)恢復(fù)時(shí), 其他客戶端來獲取鎖成功

-> redis官方:  在崩潰后使實(shí)例不可用, 至少比最大 TTL多一點(diǎn), 保證崩潰時(shí)的鎖在所有節(jié)點(diǎn)都自動(dòng)失效. [損失了可用性]

RedLock的爭(zhēng)論: 

針對(duì)RedLock的方案, 業(yè)界大佬Martin Kleppmann專門寫過一篇文章分析它的效率, 正確性和NPC問題 , redis的作者也一一反駁, 有興趣可以看文章末尾參考資料.  

NPC問題: 

Clock Drift時(shí)鐘漂移

-> redis作者: 與鎖的自動(dòng)釋放時(shí)間相比,誤差幅度很小

Network Delay網(wǎng)絡(luò)延遲

Process Pause進(jìn)程暫停(GC)

-> redis作者: 第3步已經(jīng)考慮了以上問題, 當(dāng)出現(xiàn) 加鎖總耗時(shí) > 鎖過期時(shí)間 就會(huì)認(rèn)為加鎖失敗, 而在步驟3之后出現(xiàn)GC或ND問題, 其他鎖服務(wù)比如zookeeper也這樣.

 通過以上爭(zhēng)論, 我們看到redlock確實(shí)存在一些缺點(diǎn): 

1) 性能折損, 且無(wú)法做到100%安全的分布式鎖

2) 不能橫向擴(kuò)容: 如果要提升高可用, 只能增加更多單節(jié)點(diǎn),  每個(gè)單節(jié)點(diǎn)不能再加從節(jié)點(diǎn)

4.2 Fencing Token

針對(duì)主從架構(gòu)下的分布式鎖, 前面提到的Martin Kleppmann, 在它的文章里提出了"fencing token"的解決方案: 

  • 客戶端在獲取鎖時(shí),鎖服務(wù)可以提供一個(gè)「遞增」的 token

  • 客戶端拿著這個(gè) token 去操作共享資源

  • 共享資源可以根據(jù) token 拒絕「后來者」的請(qǐng)求

這個(gè)方案要求共享資源具備"互斥"能力, 而且在分布式環(huán)境下做嚴(yán)格自增的token無(wú)疑也是個(gè)難題.

有沒有其他方案呢, 在找資料的過程中, 我發(fā)現(xiàn)Redisson較新的版本(我用的是3.25.0)提供了FencedLock.

4.3 FencedLock

它的底層獲取鎖的同時(shí), 使用 incr 命令從redis獲取自增的token: 

但是在redis集群環(huán)境下, 這樣使用incr會(huì)有可靠性問題. 當(dāng)多個(gè)客戶端同時(shí)調(diào)用incr命令時(shí),可能會(huì)出現(xiàn)并發(fā)沖突,導(dǎo)致數(shù)據(jù)不一致.

雖然redisson的官方文檔說RedLock已棄用,推薦使用Lock or FencedLock, 但如前述我覺得上述FencedLock會(huì)有可靠性問題. (如果大佬們有其他見解, 請(qǐng)賜教, 感激~)

4.4 兜底鎖

對(duì)安全性要求比較高的場(chǎng)景, 也許我們可以參考fencing token的思路在資源層再做一個(gè)兜底鎖, 比如MySQL:

在操作資源前先標(biāo)記token, 再(檢查+修改)共享資源

UPDATE
    table T
SET
    val = $new_val
WHERE
    id = $id AND current_token = $token_value;

兩種思路結(jié)合我們就擁有了一個(gè)更安全可靠的分布式鎖體系: 

  • redis分布式鎖: 作用于上層, 完成了大多數(shù)"互斥", 把大部分請(qǐng)求擋在上層, 減輕了操作資源層的壓力.
  • MySQL兜底鎖: 通過版本號(hào)或者插入鎖的方式實(shí)現(xiàn)"互斥", 避免極端情況下的并發(fā)沖突, 由于上層已經(jīng)擋住了大部分請(qǐng)求, MySQL鎖也能很好的避開它本身的缺點(diǎn).

五、總結(jié)

1) 沒有一把完美的分布式鎖, 在設(shè)計(jì)分布式鎖的時(shí)候, 需要多角度考慮它是否滿足了以下特性:

  • 獨(dú)占排他互斥
  • 防死鎖
  • 保證原子性
  • 正確性
  • 可重入
  • 容錯(cuò)分布式  

2) 如果是要求數(shù)據(jù)絕對(duì)正確的業(yè)務(wù), 資源層要做好兜底。 

到此這篇關(guān)于Redis分布式鎖及安全問題解決的文章就介紹到這了,更多相關(guān)Redis分布式鎖內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • redis保存AtomicInteger對(duì)象踩坑及解決

    redis保存AtomicInteger對(duì)象踩坑及解決

    這篇文章主要介紹了redis保存AtomicInteger對(duì)象踩坑及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-11-11
  • 利用redisson快速實(shí)現(xiàn)自定義限流注解(接口防刷)

    利用redisson快速實(shí)現(xiàn)自定義限流注解(接口防刷)

    利用redis的有序集合即Sorted?Set數(shù)據(jù)結(jié)構(gòu),構(gòu)造一個(gè)令牌桶來實(shí)施限流,而redisson已經(jīng)幫我們封裝成了RRateLimiter,通過redisson,即可快速實(shí)現(xiàn)我們的目標(biāo),這篇文章主要介紹了利用redisson快速實(shí)現(xiàn)自定義限流注解,需要的朋友可以參考下
    2024-07-07
  • Redis 命令的詳解及簡(jiǎn)單實(shí)例

    Redis 命令的詳解及簡(jiǎn)單實(shí)例

    這篇文章主要介紹了Redis 命令的詳解及簡(jiǎn)單實(shí)例的相關(guān)資料,這里提供基礎(chǔ)語(yǔ)法及使用實(shí)例,需要的朋友可以參考下
    2017-08-08
  • redis如何查看鎖是否存在

    redis如何查看鎖是否存在

    文章介紹了兩種方法來檢查Redis鎖的狀態(tài):使用GET命令查看鎖的值和使用EXISTS命令檢查鎖的存在性,這兩種方法都是通過連接到Redis服務(wù)器并執(zhí)行相應(yīng)的命令來實(shí)現(xiàn)的,GET命令用于獲取指定鍵的值,而EXISTS命令用于檢查指定鍵是否存在
    2025-01-01
  • Redis消息隊(duì)列的三種實(shí)現(xiàn)方式

    Redis消息隊(duì)列的三種實(shí)現(xiàn)方式

    本文主要介紹了Redis消息隊(duì)列的三種實(shí)現(xiàn)方式,主要包括List實(shí)現(xiàn)消息隊(duì)列,PubSub消息隊(duì)列,Stream消息隊(duì)列,具有一定的參考價(jià)值,感興趣的可以了解一下
    2023-12-12
  • Redis 大key的幾種刪除方式

    Redis 大key的幾種刪除方式

    大key刪除直接調(diào)用 del 命令刪除key,容易造成請(qǐng)求被阻塞,本文主要介紹了Redis 大key的幾種刪除方式,具有一定的參考價(jià)值,感興趣的可以了解一下
    2025-03-03
  • Redis不是一直號(hào)稱單線程效率也很高嗎,為什么又采用多線程了?

    Redis不是一直號(hào)稱單線程效率也很高嗎,為什么又采用多線程了?

    這篇文章主要介紹了Redis不是一直號(hào)稱單線程效率也很高嗎,為什么又采用多線程了的相關(guān)資料,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2021-03-03
  • redis由于目標(biāo)計(jì)算機(jī)積極拒絕,無(wú)法連接的解決

    redis由于目標(biāo)計(jì)算機(jī)積極拒絕,無(wú)法連接的解決

    這篇文章主要介紹了redis由于目標(biāo)計(jì)算機(jī)積極拒絕,無(wú)法連接的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-07-07
  • 基于Redis有序集合實(shí)現(xiàn)滑動(dòng)窗口限流的步驟

    基于Redis有序集合實(shí)現(xiàn)滑動(dòng)窗口限流的步驟

    滑動(dòng)窗口算法是一種基于時(shí)間窗口的限流算法,通過動(dòng)態(tài)地滑動(dòng)窗口,可以動(dòng)態(tài)調(diào)整限流的速率,Redis有序集合可以用來實(shí)現(xiàn)滑動(dòng)窗口限流,本文介紹基于Redis有序集合實(shí)現(xiàn)滑動(dòng)窗口限流,感興趣的朋友一起看看吧
    2024-12-12
  • 基于Redis實(shí)現(xiàn)抽獎(jiǎng)功能及問題小結(jié)

    基于Redis實(shí)現(xiàn)抽獎(jiǎng)功能及問題小結(jié)

    這篇文章主要介紹了基于Redis實(shí)現(xiàn)抽獎(jiǎng)功能,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2021-08-08

最新評(píng)論