分布式鎖--Redisson(一)

一琅拌、Redisson簡介

Redisson是架設(shè)在Redis基礎(chǔ)上的一個(gè)Java駐內(nèi)存數(shù)據(jù)網(wǎng)格(In-Memory Data Grid)揽惹。
Redisson在基于NIO的Netty框架上,充分的利用了Redis鍵值數(shù)據(jù)庫提供的一系列優(yōu)勢塞琼,在Java實(shí)用工具包中常用接口的基礎(chǔ)上,為使用者提供了一系列具有分布式特性的常用工具類。使得原本作為協(xié)調(diào)單機(jī)多線程并發(fā)程序的工具包獲得了協(xié)調(diào)分布式多機(jī)多線程并發(fā)系統(tǒng)的能力胰蝠,大大降低了設(shè)計(jì)和研發(fā)大規(guī)模分布式系統(tǒng)的難度。同時(shí)結(jié)合各富特色的分布式服務(wù),更進(jìn)一步簡化了分布式環(huán)境中程序相互之間的協(xié)作姊氓。
redisson是redis官網(wǎng)推薦的java語言實(shí)現(xiàn)分布式鎖的項(xiàng)目丐怯。當(dāng)然,redisson遠(yuǎn)不止分布式鎖翔横,還包括其他一些分布式結(jié)構(gòu)读跷。
redission可以支持redis cluster、master-slave禾唁、redis哨兵和redis單機(jī)效览。
每個(gè)Redis服務(wù)實(shí)例都能管理多達(dá)1TB的內(nèi)存。
能夠完美的在云計(jì)算環(huán)境里使用荡短,并且支持AWS ElastiCache主備版丐枉,AWS ElastiCache集群版,Azure Redis Cache和阿里云(Aliyun)的云數(shù)據(jù)庫Redis版掘托。

官網(wǎng):https://redisson.org/
GIT地址:https://github.com/redisson/redisson/wiki

二瘦锹、Maven依賴

<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson-spring-boot-starter</artifactId>
</dependency>
或
<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson</artifactId>
    <version>3.10.7</version>
</dependency>

三、Redission是怎么實(shí)現(xiàn)的闪盔?

(1)加鎖機(jī)制
假設(shè)是一個(gè)redis cluster弯院,客戶端1會(huì)根據(jù)hash算法選擇一個(gè)節(jié)點(diǎn),發(fā)送lua腳本泪掀,之所以發(fā)送lua腳本听绳,是因?yàn)橐WC原子性。

"if(redis.call('exist'),KEYS[1]==0) then" +  // KEY[1]就是key异赫,我們假設(shè)是mylock
    "redis.call('hset',KEYS[1],ARGV[2], 1);"+ // ARGV[2]就是客戶端的ID
    "redis.call('pexpire', KEYS[1], ARGV[1]);"+ // ARGV[1]代表的就是鎖key的默認(rèn)生存時(shí)間椅挣,默認(rèn)30秒
    "return nil;" + 
"end;" + 
"if (redis.call('hexists', KEYS[1], ARGV[2] == 1)) then" + 
    "redis.call('hincrby',KEYS[1],ARGV[2],1);" + 
    "reids.call('pexipre',KEYS[1], ARGV[1]);" +
    "return nil;" +
"end" + 
"return redis.call('pttl', KEYS[1])"

客戶端1成功加鎖,客戶端2來了塔拳,一看發(fā)現(xiàn)第一個(gè)if鼠证,發(fā)現(xiàn)mylock這個(gè)鎖key已經(jīng)存在了,就走第二個(gè)if蝙斜,一看發(fā)現(xiàn)沒有自己的客戶端ID名惩,所以客戶端ID會(huì)獲取到mylock這個(gè)key的剩余時(shí)間。之后客戶端2會(huì)進(jìn)入一個(gè)while循環(huán)孕荠,不停的嘗試加鎖娩鹉。
(2)watch dog自動(dòng)延期
redission還提供了watch dog線程,客戶端1加鎖的key默認(rèn)是30s稚伍,但是客戶端1業(yè)務(wù)還沒有執(zhí)行完弯予,時(shí)間就過了,客戶端1還想持有鎖的話个曙,就會(huì)啟動(dòng)一個(gè)watch dog后臺(tái)線程锈嫩,不斷的延長鎖key的生存時(shí)間受楼。
(3)可重入鎖
同時(shí),如果客戶端1重復(fù)加鎖呼寸,也是支持艳汽,無非就是hset +1,代表的加鎖的次數(shù)+1对雪,不過代碼中記得要unlock()掉河狐。
(4)釋放鎖
釋放鎖,其實(shí)就是將加鎖次數(shù)-1瑟捣,如果發(fā)現(xiàn)加鎖次數(shù)是0馋艺,說明這個(gè)客戶端已經(jīng)不再持有鎖,就會(huì)執(zhí)行del mylock命令迈套,從redis里把這個(gè)kv刪掉捐祠,這樣客戶端2就可以加鎖了。
(5)缺點(diǎn)
因?yàn)槭莚edis cluster桑李,這個(gè)kv會(huì)被異步復(fù)制給其他節(jié)點(diǎn)踱蛀。但是在這過程中主節(jié)點(diǎn)掛了,還沒來得及復(fù)制贵白。雖然客戶端1以為加鎖成功了星岗,其實(shí)這個(gè)key已經(jīng)丟失。
主備切換后戒洼,客戶端2也來加鎖,也成功了允华,這樣就導(dǎo)致了多個(gè)客戶端對(duì)一個(gè)分布式鎖完成了加鎖圈浇,可能會(huì)造成臟數(shù)據(jù)。

三靴寂、配置

  1. 單機(jī)配置
@Configuration
public class RedissonConfig {
    @Value("${spring.redis.host}")
    private String host;
    @Value("${spring.redis.port}")
    private String port;

    @Bean
    public RedissonClient redissonClient(){
        Config config = new Config();
        config.useSingleServer().setAddress("redis://"+host+":"+port);
        return Redisson.create(config);
    }
}

四磷蜀、分布式鎖

  1. 可重入鎖(Reentrant Lock)
    Redisson的分布式可重入鎖RLock Java對(duì)象實(shí)現(xiàn)了java.util.concurrent.locks.Lock接口,同時(shí)還支持自動(dòng)過期解鎖百炬。
public void testReentrantLock(RedissonClient redisson){

        RLock lock = redisson.getLock("anyLock");
        try{
            // 1. 最常見的使用方法
            //lock.lock();

            // 2. 支持過期解鎖功能,10秒鐘以后自動(dòng)解鎖, 無需調(diào)用unlock方法手動(dòng)解鎖
            //lock.lock(10, TimeUnit.SECONDS);

            // 3. 嘗試加鎖褐隆,最多等待3秒,上鎖以后10秒自動(dòng)解鎖
            boolean res = lock.tryLock(3, 10, TimeUnit.SECONDS);
            if(res){    //成功
                // do your business

            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }

    }

Redisson同時(shí)還為分布式鎖提供了異步執(zhí)行的相關(guān)方法:

public void testAsyncReentrantLock(RedissonClient redisson){
        RLock lock = redisson.getLock("anyLock");
        try{
            lock.lockAsync();
            lock.lockAsync(10, TimeUnit.SECONDS);
            Future<Boolean> res = lock.tryLockAsync(3, 10, TimeUnit.SECONDS);

            if(res.get()){
                // do your business

            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }

    }
  1. 公平鎖(Fair Lock)
    Redisson分布式可重入公平鎖也是實(shí)現(xiàn)了java.util.concurrent.locks.Lock接口的一種RLock對(duì)象剖踊。在提供了自動(dòng)過期解鎖功能的同時(shí)庶弃,保證了當(dāng)多個(gè)Redisson客戶端線程同時(shí)請(qǐng)求加鎖時(shí),優(yōu)先分配給先發(fā)出請(qǐng)求的線程德澈。
public void testFairLock(RedissonClient redisson){

        RLock fairLock = redisson.getFairLock("anyLock");
        try{
            // 最常見的使用方法
            fairLock.lock();

            // 支持過期解鎖功能, 10秒鐘以后自動(dòng)解鎖,無需調(diào)用unlock方法手動(dòng)解鎖
            fairLock.lock(10, TimeUnit.SECONDS);

            // 嘗試加鎖歇攻,最多等待100秒,上鎖以后10秒自動(dòng)解鎖
            boolean res = fairLock.tryLock(100, 10, TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            fairLock.unlock();
        }
    }

Redisson同時(shí)還為分布式可重入公平鎖提供了異步執(zhí)行的相關(guān)方法:

RLock fairLock = redisson.getFairLock("anyLock");
fairLock.lockAsync();
fairLock.lockAsync(10, TimeUnit.SECONDS);
Future<Boolean> res = fairLock.tryLockAsync(100, 10, TimeUnit.SECONDS);
  1. 聯(lián)鎖(MultiLock)
    Redisson的RedissonMultiLock對(duì)象可以將多個(gè)RLock對(duì)象關(guān)聯(lián)為一個(gè)聯(lián)鎖梆造,每個(gè)RLock對(duì)象實(shí)例可以來自于不同的Redisson實(shí)例缴守。
public void testMultiLock(RedissonClient redisson1,
                              RedissonClient redisson2, RedissonClient redisson3){

        RLock lock1 = redisson1.getLock("lock1");
        RLock lock2 = redisson2.getLock("lock2");
        RLock lock3 = redisson3.getLock("lock3");

        RedissonMultiLock lock = new RedissonMultiLock(lock1, lock2, lock3);

        try {
            // 同時(shí)加鎖:lock1 lock2 lock3, 所有的鎖都上鎖成功才算成功。
            lock.lock();

            // 嘗試加鎖,最多等待100秒屡穗,上鎖以后10秒自動(dòng)解鎖
            boolean res = lock.tryLock(100, 10, TimeUnit.SECONDS);

        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

4. 紅鎖(RedLock)

Redisson的RedissonRedLock對(duì)象實(shí)現(xiàn)了Redlock介紹的加鎖算法贴捡。該對(duì)象也可以用來將多個(gè)RLock
對(duì)象關(guān)聯(lián)為一個(gè)紅鎖,每個(gè)RLock對(duì)象實(shí)例可以來自于不同的Redisson實(shí)例村砂。

    public void testRedLock(RedissonClient redisson1,
                              RedissonClient redisson2, RedissonClient redisson3){

        RLock lock1 = redisson1.getLock("lock1");
        RLock lock2 = redisson2.getLock("lock2");
        RLock lock3 = redisson3.getLock("lock3");

        RedissonRedLock lock = new RedissonRedLock(lock1, lock2, lock3);
      try {
            // 同時(shí)加鎖:lock1 lock2 lock3, 紅鎖在大部分節(jié)點(diǎn)上加鎖成功就算成功烂斋。
            lock.lock();

            // 嘗試加鎖,最多等待100秒箍镜,上鎖以后10秒自動(dòng)解鎖
            boolean res = lock.tryLock(100, 10, TimeUnit.SECONDS);

        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
  1. 讀寫鎖(ReadWriteLock)
    Redisson的分布式可重入讀寫鎖RReadWriteLock Java對(duì)象實(shí)現(xiàn)了java.util.concurrent.locks.ReadWriteLock接口源祈。同時(shí)還支持自動(dòng)過期解鎖。該對(duì)象允許同時(shí)有多個(gè)讀取鎖色迂,但是最多只能有一個(gè)寫入鎖香缺。
RReadWriteLock rwlock = redisson.getLock("anyRWLock");
// 最常見的使用方法
rwlock.readLock().lock();
// 或
rwlock.writeLock().lock();

// 支持過期解鎖功能
// 10秒鐘以后自動(dòng)解鎖
// 無需調(diào)用unlock方法手動(dòng)解鎖
rwlock.readLock().lock(10, TimeUnit.SECONDS);
// 或
rwlock.writeLock().lock(10, TimeUnit.SECONDS);

// 嘗試加鎖,最多等待100秒歇僧,上鎖以后10秒自動(dòng)解鎖
boolean res = rwlock.readLock().tryLock(100, 10, TimeUnit.SECONDS);
// 或
boolean res = rwlock.writeLock().tryLock(100, 10, TimeUnit.SECONDS);
...
lock.unlock();
  1. 信號(hào)量(Semaphore)
    Redisson的分布式信號(hào)量(Semaphore)Java對(duì)象RSemaphore采用了與java.util.concurrent.Semaphore相似的接口和用法图张。
RSemaphore semaphore = redisson.getSemaphore("semaphore");
semaphore.acquire();
//或
semaphore.acquireAsync();
semaphore.acquire(23);
semaphore.tryAcquire();
//或
semaphore.tryAcquireAsync();
semaphore.tryAcquire(23, TimeUnit.SECONDS);
//或
semaphore.tryAcquireAsync(23, TimeUnit.SECONDS);
semaphore.release(10);
semaphore.release();
//或
semaphore.releaseAsync();
  1. 可過期性信號(hào)量(PermitExpirableSemaphore)
    Redisson的可過期性信號(hào)量(PermitExpirableSemaphore)實(shí)在RSemaphore對(duì)象的基礎(chǔ)上,為每個(gè)信號(hào)增加了一個(gè)過期時(shí)間诈悍。每個(gè)信號(hào)可以通過獨(dú)立的ID來辨識(shí)祸轮,釋放時(shí)只能通過提交這個(gè)ID才能釋放。
RPermitExpirableSemaphore semaphore = redisson.getPermitExpirableSemaphore("mySemaphore");
String permitId = semaphore.acquire();
// 獲取一個(gè)信號(hào)侥钳,有效期只有2秒鐘适袜。
String permitId = semaphore.acquire(2, TimeUnit.SECONDS);
// ...
semaphore.release(permitId);
  1. 閉鎖(CountDownLatch)
    Redisson的分布式閉鎖(CountDownLatch)Java對(duì)象RCountDownLatch采用了與java.util.concurrent.CountDownLatch相似的接口和用法。
RCountDownLatch latch = redisson.getCountDownLatch("anyCountDownLatch");
latch.trySetCount(1);
latch.await();

// 在其他線程或其他JVM里
RCountDownLatch latch = redisson.getCountDownLatch("anyCountDownLatch");
latch.countDown();
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末舷夺,一起剝皮案震驚了整個(gè)濱河市苦酱,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌给猾,老刑警劉巖疫萤,帶你破解...
    沈念sama閱讀 211,194評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異敢伸,居然都是意外死亡扯饶,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,058評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門池颈,熙熙樓的掌柜王于貴愁眉苦臉地迎上來尾序,“玉大人,你說我怎么就攤上這事躯砰《拙鳎” “怎么了?”我有些...
    開封第一講書人閱讀 156,780評(píng)論 0 346
  • 文/不壞的土叔 我叫張陵弃揽,是天一觀的道長脯爪。 經(jīng)常有香客問我则北,道長,這世上最難降的妖魔是什么痕慢? 我笑而不...
    開封第一講書人閱讀 56,388評(píng)論 1 283
  • 正文 為了忘掉前任尚揣,我火速辦了婚禮,結(jié)果婚禮上掖举,老公的妹妹穿的比我還像新娘快骗。我一直安慰自己,他們只是感情好塔次,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,430評(píng)論 5 384
  • 文/花漫 我一把揭開白布方篮。 她就那樣靜靜地躺著,像睡著了一般励负。 火紅的嫁衣襯著肌膚如雪藕溅。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,764評(píng)論 1 290
  • 那天继榆,我揣著相機(jī)與錄音巾表,去河邊找鬼。 笑死略吨,一個(gè)胖子當(dāng)著我的面吹牛集币,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播翠忠,決...
    沈念sama閱讀 38,907評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼鞠苟,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了秽之?” 一聲冷哼從身側(cè)響起偶妖,我...
    開封第一講書人閱讀 37,679評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎政溃,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體态秧,經(jīng)...
    沈念sama閱讀 44,122評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡董虱,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,459評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了申鱼。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片愤诱。...
    茶點(diǎn)故事閱讀 38,605評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖捐友,靈堂內(nèi)的尸體忽然破棺而出淫半,到底是詐尸還是另有隱情,我是刑警寧澤匣砖,帶...
    沈念sama閱讀 34,270評(píng)論 4 329
  • 正文 年R本政府宣布科吭,位于F島的核電站昏滴,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏对人。R本人自食惡果不足惜谣殊,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,867評(píng)論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望牺弄。 院中可真熱鬧姻几,春花似錦、人聲如沸势告。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,734評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽咱台。三九已至络拌,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間吵护,已是汗流浹背盒音。 一陣腳步聲響...
    開封第一講書人閱讀 31,961評(píng)論 1 265
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留馅而,地道東北人祥诽。 一個(gè)月前我還...
    沈念sama閱讀 46,297評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像瓮恭,于是被迫代替她去往敵國和親蝗碎。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,472評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容

  • # 產(chǎn)生背景 Distributed locks are a very useful primitive in m...
    Gopher_39b2閱讀 2,139評(píng)論 0 22
  • 簡單使用 基本 依賴 可運(yùn)行示例代碼 集成spring boot 示例項(xiàng)目地址:https://github.co...
    wine_5664閱讀 1,823評(píng)論 0 4
  • 簡述 使用分布式鎖的目的: 提高效率:使用分布式鎖避免不同節(jié)點(diǎn)重復(fù)性的操作息堂,比如:推送脐区、定時(shí)任務(wù)等 保證正確性:避...
    Jerry_06ed閱讀 554評(píng)論 0 4
  • 紐約交通拉雜談 在談到這個(gè)話題前,必須說明登澜,由于文章所涉及的內(nèi)容缺乏大數(shù)據(jù)支持阔挠。所以,不會(huì)出書脑蠕,不能見...
    易工閱讀 883評(píng)論 0 0
  • 他和她的故事谴仙,是在某一個(gè)黃昏里開始的迂求! 那時(shí)候的她,還是一如既往地樂觀晃跺,做一個(gè)安靜的女子揩局,在清晨里迎著朝霞奔跑,在...
    黍苗隰桑閱讀 204評(píng)論 0 0