分布式鎖

通過本文檔,你將會了解到

  • 本地鎖不香么?你搞什么分布式鎖?
  • 分布式鎖的產(chǎn)生背景?
  • 分布式鎖怎么玩?以及玩起來的注意事項

1、 回顧

上次我們講到了,本地緩存的缺陷,所以嘍,直接用一個緩存中間件redis嘍。關(guān)于怎么使用,就不扯了,大家都會。接下來主要講的是我們項目中使用到的分布式鎖。

2、鎖

2.1、本地鎖

本地鎖

說到加鎖 我們有很多方式,比如synchronized,JUC包下的各種各樣的鎖,但是這種鎖只能鎖住當(dāng)前進(jìn)程,我們部署10臺機器,本地鎖就玩不轉(zhuǎn)了。所以分布式鎖說那我來吧,這也是我們項目中使用到的。我們所有的機器都先去占坑,如果占坑成功,那么你想怎么樣就怎么樣。

所以嘍要使用同一個鎖,我們使用redis分布式鎖,當(dāng)然也有ZK鎖。我們講我們項目中使用到的redis分布式鎖。我們可以同時去一個地方“占坑”,如果占到,就執(zhí)行邏輯。否則就必須等待,直到釋放鎖。 “占坑”可以去redis,可以去數(shù)據(jù)庫,可以去任何大家都能訪問的地方。 等待可以自旋的方式。

上次我們講到緩存擊穿的時候說到過:加鎖。大量并發(fā)只讓一個人去查,其他人等待,查到之后釋放鎖,其他人獲取到鎖,先查緩存,就會有數(shù)據(jù),不用去查數(shù)據(jù)庫

2.2、分布式鎖

2.2.1、介紹

分布式鎖的原理就是占坑唄,所有的人都想上廁所,那誰占到坑,那誰上嘍,分布式鎖大概就是這個原理搞的。

分布式鎖

廢話不多少,直接上菜唄,Redis里面那個是占坑的?說到學(xué)習(xí)一門新的知識,那么權(quán)威肯定是官網(wǎng)嘍。
redis官網(wǎng)

http://www.redis.cn/
http://www.redis.cn/commands/set.html

有個命令比較有特色就是set命令 這個就是占坑的命令。這個坑占起來還有講究,hahah
SET 里面放個key 一個value 這個后面可是有可選參數(shù)哦,有的是過期時間,有一個是NX,所以綜上所述
占坑命令就是
set lock haha NX

SET key value [EX seconds] [PX milliseconds] [NX|XX]

EX seconds – Set the specified expire time, in seconds.
PX milliseconds – Set the specified expire time, in milliseconds.
NX – Only set the key if it does not already exist.
XX – Only set the key if it already exist.
EX seconds – 設(shè)置鍵key的過期時間,單位時秒
PX milliseconds – 設(shè)置鍵key的過期時間,單位時毫秒
NX – 只有鍵key不存在的時候才會設(shè)置key的值
XX – 只有鍵key存在的時候才會設(shè)置key的值


注意: 由于SET命令加上選項已經(jīng)可以完全取代SETNX, SETEX, PSETEX的功能,所以在將來的版本中,redis可能會不推薦使用并且最終拋棄這幾個命令。

2.2.2、分布式鎖的演進(jìn)過程

巧了,我懂了,直接用set lock haha NX 分布式鎖搞的,分享結(jié)束。你覺得會是這么簡單么?不要太天真,到時候發(fā)現(xiàn)小丑竟是我自己hahaha。

2.2.2.1、青銅玩家

來上菜。我們先看青銅玩家怎么玩?

Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent("lock", "11111");
if (lock) {
 
    try {
        //加鎖成功...執(zhí)行業(yè)務(wù)
        XXXXXXX
        //刪除
        stringRedisTemplate.delete(XXXXXX);
    }else{
        //重試 自旋
        1.自己調(diào)取自己
        2.可以休眠1S
    }

你看看青銅玩家,一看就是新手。一眼就看出來的錯誤就是異常了沒人解鎖了。
解決:

  • 設(shè)置鎖的自動過期,即使沒有刪除,會自動刪除。
  • try 包裹 finally解鎖

2.2.2.2、白銀玩家

 Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent("lock", uuid,300,TimeUnit.SECONDS);

if (lock) {
 
    try {
        //加鎖成功...執(zhí)行業(yè)務(wù)
    }else{
        //重試 自旋
        1.自己調(diào)取自己
        2.可以休眠1S
    } finally{
     //刪除
        stringRedisTemplate.delete(XXXXXX);
}

你看看白銀玩家,有點那么個意思了是吧?那白銀玩家有沒有問題呢?階段才是白銀了,那肯定是有問題的,關(guān)鍵是問題在哪里?

1、刪除鎖直接刪除???
如果由于業(yè)務(wù)時間很長,鎖自己過期了,我們直接刪除,有可能把別人正在持有的鎖刪除了。

2、解決:

  • 占鎖的時候,值指定為uuid,每個人匹配是自己的鎖才刪除。。

2.2.2.2、黃金玩家

黃金玩家

關(guān)鍵是問題在哪里?

問題:
1、如果正好判斷是當(dāng)前值,正要刪除鎖的時候,鎖已經(jīng)過期, 別人已經(jīng)設(shè)置到了新的值。那么我們刪除的是別人的鎖。

2、解決:

  • 刪除鎖必須保證原子性。使用redis+Lua腳本完成

2.2.2.2、鉆石玩家

//1、占分布式鎖。去redis占坑      設(shè)置過期時間必須和加鎖是同步的,保證原子性(避免死鎖)
String uuid = UUID.randomUUID().toString();
Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent("lock", uuid,300,TimeUnit.SECONDS);
if (lock) {
    System.out.println("獲取分布式鎖成功...");
    Map<String, List<Catelog2Vo>> dataFromDb = null;
    try {
        //加鎖成功...執(zhí)行業(yè)務(wù)
        dataFromDb = getDataFromDb();
    } finally {
        String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";

        //刪除鎖
        stringRedisTemplate.execute(new DefaultRedisScript<Long>(script, Long.class), Arrays.asList("lock"), uuid);

    }

當(dāng)然線上你可以這么寫,但是畢竟是鉆石玩家,我們還有更好的玩法。

2.3、Redisson 作為分布式鎖

這個也是我們項目中使用到的。廢話不多說,第一步打開官網(wǎng),開啟尋寶之路。

https://www.redis.io/topics/distlock

分布式鎖的介紹頁面


分布式鎖

2.3.1 Redisson 是什么

一種 可重入、持續(xù)阻塞、獨占式的 分布式鎖協(xié)調(diào)框架。
Redisson的宗旨是促進(jìn)使用者對Redis的關(guān)注分離(Separation of Concern),從而讓使用者能夠?qū)⒕Ω械胤旁谔幚順I(yè)務(wù)邏輯上。

特點:

  • 可重入
    拿到鎖的線程后續(xù)拿鎖可跳過獲取鎖的步驟,只進(jìn)行value+1的步驟。
  • 持續(xù)阻塞
    獲取不到鎖的線程,會在一定時間內(nèi)等待鎖。
  • 獨占式
    同一環(huán)境下理論上只能有一個線程可以獲取到鎖

比較爽的一點是什么?
基于Redis的Redisson分布式可重入鎖RLock Java對象實現(xiàn)了java.util.concurrent.locks.Lock接口。

這句話什么意思?如果你已經(jīng)了解了JUC包下的各種鎖,什么ReentrantLock,信號量什么的。恭喜你無縫切換。就像你換手機一樣,iPhone12換iPhone13 像流水切換一樣自然。以前怎么用,現(xiàn)在還怎么用。

關(guān)于并發(fā)編程JUC的知識不在本次分享范圍內(nèi)。

      //1、獲取一把鎖,只要鎖的名字一樣,就是同一把鎖
        RLock myLock = redisson.getLock("my-lock");

        //2、加鎖
        myLock.lock();      //阻塞式等待。默認(rèn)加的鎖都是30s
        //1)、鎖的自動續(xù)期,如果業(yè)務(wù)超長,運行期間自動鎖上新的30s。不用擔(dān)心業(yè)務(wù)時間長,鎖自動過期被刪掉
        //2)、加鎖的業(yè)務(wù)只要運行完成,就不會給當(dāng)前鎖續(xù)期,即使不手動解鎖,鎖默認(rèn)會在30s內(nèi)自動過期,不會產(chǎn)生死鎖問題
        // myLock.lock(10,TimeUnit.SECONDS);   //10秒鐘自動解鎖,自動解鎖時間一定要大于業(yè)務(wù)執(zhí)行時間
        //問題:在鎖時間到了以后,不會自動續(xù)期
        //1、如果我們傳遞了鎖的超時時間,就發(fā)送給redis執(zhí)行腳本,進(jìn)行占鎖,默認(rèn)超時就是 我們制定的時間
        //2、如果我們指定鎖的超時時間,就使用 lockWatchdogTimeout = 30 * 1000 【看門狗默認(rèn)時間】
        //只要占鎖成功,就會啟動一個定時任務(wù)【重新給鎖設(shè)置過期時間,新的過期時間就是看門狗的默認(rèn)時間】,每隔10秒都會自動的再次續(xù)期,續(xù)成30秒
        // internalLockLeaseTime 【看門狗時間】 / 3, 10s
        try {
            System.out.println("加鎖成功,執(zhí)行業(yè)務(wù)..." + Thread.currentThread().getId());
            try { TimeUnit.SECONDS.sleep(20); } catch (InterruptedException e) { e.printStackTrace(); }
        } catch (Exception ex) {
            ex.printStackTrace();
        } finally {
            //3、解鎖  假設(shè)解鎖代碼沒有運行,Redisson會不會出現(xiàn)死鎖
            System.out.println("釋放鎖..." + Thread.currentThread().getId());
            myLock.unlock();
        }


關(guān)于看門狗,就是業(yè)務(wù)執(zhí)行需要10秒,還沒執(zhí)行完就解鎖了?看門狗自動幫你續(xù)命時間(這個是一個定時任務(wù),沒10秒執(zhí)行一次看看要不要續(xù)命)當(dāng)前線程活著就續(xù)命,線程(而key的組成應(yīng)該是:{uuid}:{threadid})只有沒有指定過去時間才會啟動。

最佳實踐:我們指定過期時間,性能會有一點增加,指定3秒過期時間,3秒都沒執(zhí)行完,那早就超時了什么的。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • @[toc] 一、緩存 1、緩存使用 為了系統(tǒng)性能的提升,我們一般都會將部分?jǐn)?shù)據(jù)放入緩存中,加速訪問。而db承擔(dān)數(shù)...
    runewbie閱讀 889評論 0 1
  • 1 Redis分布式鎖的特性 在實現(xiàn)分布式鎖時,需要保證鎖實現(xiàn)的安全性和可靠性?;谶@點特點,實現(xiàn)分布式鎖需要具備...
    愛健身的兔子閱讀 1,105評論 0 0
  • 1. 分布式應(yīng)用并發(fā)問題 分布式應(yīng)用進(jìn)行邏輯處理時經(jīng)常會遇到并發(fā)問題,比如下面這個例子。 在Redis里對"acc...
    逍遙白亦閱讀 473評論 0 5
  • 1 本地鎖 常用的即 synchronize 或 Lock 等 JDK 自帶的鎖,只能鎖住當(dāng)前進(jìn)程,僅適用于單體架...
    半壺雪閱讀 359評論 0 0
  • 我是黑夜里大雨紛飛的人啊 1 “又到一年六月,有人笑有人哭,有人歡樂有人憂愁,有人驚喜有人失落,有的覺得收獲滿滿有...
    陌忘宇閱讀 8,814評論 28 54

友情鏈接更多精彩內(nèi)容