redisson分布式鎖與同步器教程

簡單使用

  • 基本
// 1. 創(chuàng)建配置對象
Config = ...

// 2. 創(chuàng)建redisson實例
RedissonClient redisson = Redisson.create(config);

// 3. 獲得鎖對象
RLock lock = redisson.getLock("myLock");

// 4. 上鎖
lock.lock();

// 做你想做的须肆。节值。逻恐。锤岸。

// 5.解鎖
lock.unlock();
  • 依賴
<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson</artifactId>
    <version>3.4.3</version>
</dependency>
  • 可運行示例代碼
public class RedissonTest {
    private static Logger logger = LoggerFactory.getLogger(RedissonTest.class);

    private String address = "redis://localhost:6379";
    private int connectionMinimumIdleSize = 10;
    private int idleConnectionTimeout = 10000;
    private int pingTimeout = 1000;
    private int connectTimeout = 10000;
    private int timeout = 3000;
    private int retryAttempts = 3;
    private int retryInterval = 1500;
    private int reconnectionTimeout = 3000;
    private int failedAttempts = 3;
    private String password = null;
    private int subscriptionsPerConnection = 5;
    private String clientName = null;
    private int subscriptionConnectionMinimumIdleSize = 1;
    private int subscriptionConnectionPoolSize = 50;
    private int connectionPoolSize = 64;
    private int database = 0;
    private boolean dnsMonitoring = false;
    private int dnsMonitoringInterval = 5000;

    private int thread; //當前處理核數(shù)量 * 2

    private String codec = "org.redisson.codec.JsonJacksonCodec";

    RedissonClient redisson() throws Exception {
        Config config = new Config();
        config.useSingleServer().setAddress(address)
                .setConnectionMinimumIdleSize(connectionMinimumIdleSize)
                .setConnectionPoolSize(connectionPoolSize)
                .setDatabase(database)
                .setDnsMonitoring(dnsMonitoring)
                .setDnsMonitoringInterval(dnsMonitoringInterval)
                .setSubscriptionConnectionMinimumIdleSize(subscriptionConnectionMinimumIdleSize)
                .setSubscriptionConnectionPoolSize(subscriptionConnectionPoolSize)
                .setSubscriptionsPerConnection(subscriptionsPerConnection)
                .setClientName(clientName)
                .setFailedAttempts(failedAttempts)
                .setRetryAttempts(retryAttempts)
                .setRetryInterval(retryInterval)
                .setReconnectionTimeout(reconnectionTimeout)
                .setTimeout(timeout)
                .setConnectTimeout(connectTimeout)
                .setIdleConnectionTimeout(idleConnectionTimeout)
                .setPingTimeout(pingTimeout)
                .setPassword(password);
        Codec codec = (Codec) ClassUtils.forName("org.redisson.codec.JsonJacksonCodec", ClassUtils.getDefaultClassLoader()).newInstance();
        config.setCodec(codec);
        config.setThreads(thread);
        config.setEventLoopGroup(new NioEventLoopGroup());
        config.setUseLinuxNativeEpoll(false);
        return Redisson.create(config);
    }

    @Test
    public void testGetLock() throws Exception {
        RedissonClient redissonClient = redisson();

        RLock lock = redissonClient.getLock("TEST");
        try {
            lock.lock();
            logger.info("Request Thread - " + Thread.currentThread().getName() + " locked and begun...");
            Thread.sleep(5000); // 5 sec
            logger.info("Request Thread - " + Thread.currentThread().getName() + " ended successfully...");
        } catch (Exception ex) {
            logger.error("Error occurred");
        } finally {
            lock.unlock();
            logger.info("Request Thread - " + Thread.currentThread().getName() + " unlocked...");
        }
    }
}

集成spring boot

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.redisson</groupId>
            <artifactId>redisson</artifactId>
            <version>3.4.3</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
  • 配置
@Configuration
public class RedissonConfig {

    private String address = "redis://localhost:6379";
    private int connectionMinimumIdleSize = 10;
    private int idleConnectionTimeout = 10000;
    private int pingTimeout = 1000;
    private int connectTimeout = 10000;
    private int timeout = 3000;
    private int retryAttempts = 3;
    private int retryInterval = 1500;
    private int reconnectionTimeout = 3000;
    private int failedAttempts = 3;
    private String password = null;
    private int subscriptionsPerConnection = 5;
    private String clientName = null;
    private int subscriptionConnectionMinimumIdleSize = 1;
    private int subscriptionConnectionPoolSize = 50;
    private int connectionPoolSize = 64;
    private int database = 0;
    private boolean dnsMonitoring = false;
    private int dnsMonitoringInterval = 5000;

    private int thread; //當前處理核數(shù)量 * 2

    private String codec = "org.redisson.codec.JsonJacksonCodec";

    @Bean(destroyMethod = "shutdown")
    RedissonClient redisson() throws Exception {
        Config config = new Config();
        config.useSingleServer().setAddress(address)
                .setConnectionMinimumIdleSize(connectionMinimumIdleSize)
                .setConnectionPoolSize(connectionPoolSize)
                .setDatabase(database)
                .setDnsMonitoring(dnsMonitoring)
                .setDnsMonitoringInterval(dnsMonitoringInterval)
                .setSubscriptionConnectionMinimumIdleSize(subscriptionConnectionMinimumIdleSize)
                .setSubscriptionConnectionPoolSize(subscriptionConnectionPoolSize)
                .setSubscriptionsPerConnection(subscriptionsPerConnection)
                .setClientName(clientName)
                .setFailedAttempts(failedAttempts)
                .setRetryAttempts(retryAttempts)
                .setRetryInterval(retryInterval)
                .setReconnectionTimeout(reconnectionTimeout)
                .setTimeout(timeout)
                .setConnectTimeout(connectTimeout)
                .setIdleConnectionTimeout(idleConnectionTimeout)
                .setPingTimeout(pingTimeout)
                .setPassword(password);
        Codec codec = (Codec) ClassUtils.forName("org.redisson.codec.JsonJacksonCodec", ClassUtils.getDefaultClassLoader()).newInstance();
        config.setCodec(codec);
        config.setThreads(thread);
        config.setEventLoopGroup(new NioEventLoopGroup());
        config.setUseLinuxNativeEpoll(false);
        return Redisson.create(config);
    }
}

分布式鎖使用場景

  • 場景1
某集群服務提供一組任務阶界,A請求隨機從集群中的機器1任務組中獲取一個任務摔敛;B請求隨機從集群中的機器2任務組中獲取一個任務堪夭。 
在理想的情況下巧勤,A從任務組中挑選一個任務,任務組刪除該任務票顾,B從剩下的的任務中再挑一個础浮,任務組刪除該任務。 
同樣的奠骄,在真實情況下豆同,如果不做任何處理,可能會出現(xiàn)A和B挑中了同一個任務的情況含鳞。
  • 場景2
以流量業(yè)務場景中例子來說明影锈,手機用戶可以在手機App端、網(wǎng)上營業(yè)廳民晒、wap手廳進行流量業(yè)務的操作
如果發(fā)現(xiàn)該用戶沒有流量賬戶的時候精居,會首先給該用戶創(chuàng)建一個專門的流量賬戶,如果用戶在app端潜必、網(wǎng)上營業(yè)廳端同時操作的時候,可能會給該用戶創(chuàng)建2個賬戶沃但;
  • 場景3
一個公共集團賬戶磁滚,下面包含很多賬戶,給下面賬戶充值的時候宵晚,會對該集團賬本進行資金扣減垂攘,高并發(fā)多請求的時候會到導致并發(fā)失敗,這時候為了減少失敗率淤刃,提升QPS/TPS晒他,同樣需要分布式鎖 

實質(zhì):在多線程中加鎖是線程進程資源的分配,而到分布式鎖這里是單個服務器進程)對集群共有資源的分配

官方詳細教程

8.1. 可重入鎖(Reentrant Lock)

基于Redis的Redisson分布式可重入鎖RLock Java對象實現(xiàn)了java.util.concurrent.locks.Lock接口逸贾。

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

大家都知道陨仅,如果負責儲存這個分布式鎖的Redis節(jié)點宕機以后,而且這個鎖正好處于鎖住的狀態(tài)時铝侵,這個鎖會出現(xiàn)鎖死的狀態(tài)灼伤。為了避免這種情況的發(fā)生,Redisson內(nèi)部提供了一個監(jiān)控鎖的看門狗咪鲜,它的作用是在Redisson實例被關(guān)閉前狐赡,不斷的延長鎖的有效期。默認情況下疟丙,看門狗的檢查鎖的超時時間是30秒鐘颖侄,也可以通過修改Config.lockWatchdogTimeout來另行指定鸟雏。

另外Redisson還通過加鎖的方法提供了leaseTime的參數(shù)來指定加鎖的時間。超過這個時間后鎖便自動解開了览祖。

// 加鎖以后10秒鐘自動解鎖
// 無需調(diào)用unlock方法手動解鎖
lock.lock(10, TimeUnit.SECONDS);

// 嘗試加鎖孝鹊,最多等待100秒,上鎖以后10秒自動解鎖
boolean res = lock.tryLock(100, 10, TimeUnit.SECONDS);
...
lock.unlock();

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

RLock lock = redisson.getLock("anyLock");
lock.lockAsync();
lock.lockAsync(10, TimeUnit.SECONDS);
Future<Boolean> res = lock.tryLockAsync(100, 10, TimeUnit.SECONDS);

RLock對象完全符合Java的Lock規(guī)范穴墅。也就是說只有擁有鎖的進程才能解鎖惶室,其他進程解鎖則會拋出IllegalMonitorStateException錯誤。但是如果遇到需要其他進程也能解鎖的情況玄货,請使用分布式信號量Semaphore 對象.

8.2. 公平鎖(Fair Lock)

基于Redis的Redisson分布式可重入公平鎖也是實現(xiàn)了java.util.concurrent.locks.Lock接口的一種RLock對象皇钞。它保證了當多個Redisson客戶端線程同時請求加鎖時,優(yōu)先分配給先發(fā)出請求的線程松捉。

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

大家都知道夹界,如果負責儲存這個分布式鎖的Redis節(jié)點宕機以后,而且這個鎖正好處于鎖住的狀態(tài)時隘世,這個鎖會出現(xiàn)鎖死的狀態(tài)可柿。為了避免這種情況的發(fā)生,Redisson內(nèi)部提供了一個監(jiān)控鎖的看門狗丙者,它的作用是在Redisson實例被關(guān)閉前复斥,不斷的延長鎖的有效期。默認情況下械媒,看門狗的檢查鎖的超時時間是30秒鐘目锭,也可以通過修改Config.lockWatchdogTimeout來另行指定。

另外Redisson還通過加鎖的方法提供了leaseTime的參數(shù)來指定加鎖的時間纷捞。超過這個時間后鎖便自動解開了痢虹。

// 10秒鐘以后自動解鎖
// 無需調(diào)用unlock方法手動解鎖
fairLock.lock(10, TimeUnit.SECONDS);

// 嘗試加鎖,最多等待100秒主儡,上鎖以后10秒自動解鎖
boolean res = fairLock.tryLock(100, 10, TimeUnit.SECONDS);
...
fairLock.unlock();

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

RLock fairLock = redisson.getFairLock("anyLock");
fairLock.lockAsync();
fairLock.lockAsync(10, TimeUnit.SECONDS);
Future<Boolean> res = fairLock.tryLockAsync(100, 10, TimeUnit.SECONDS);

8.3. 聯(lián)鎖(MultiLock)

基于Redis的Redisson分布式聯(lián)鎖RedissonMultiLock對象可以將多個RLock對象關(guān)聯(lián)為一個聯(lián)鎖奖唯,每個RLock對象實例可以來自于不同的Redisson實例。

RLock lock1 = redissonInstance1.getLock("lock1");
RLock lock2 = redissonInstance2.getLock("lock2");
RLock lock3 = redissonInstance3.getLock("lock3");

RedissonMultiLock lock = new RedissonMultiLock(lock1, lock2, lock3);
// 同時加鎖:lock1 lock2 lock3
// 所有的鎖都上鎖成功才算成功糜值。
lock.lock();
...
lock.unlock();

大家都知道丰捷,如果負責儲存某些分布式鎖的某些Redis節(jié)點宕機以后,而且這些鎖正好處于鎖住的狀態(tài)時臀玄,這些鎖會出現(xiàn)鎖死的狀態(tài)瓢阴。為了避免這種情況的發(fā)生,Redisson內(nèi)部提供了一個監(jiān)控鎖的看門狗健无,它的作用是在Redisson實例被關(guān)閉前荣恐,不斷的延長鎖的有效期。默認情況下,看門狗的檢查鎖的超時時間是30秒鐘叠穆,也可以通過修改Config.lockWatchdogTimeout來另行指定少漆。

另外Redisson還通過加鎖的方法提供了leaseTime的參數(shù)來指定加鎖的時間。超過這個時間后鎖便自動解開了硼被。

RedissonMultiLock lock = new RedissonMultiLock(lock1, lock2, lock3);
// 給lock1示损,lock2,lock3加鎖嚷硫,如果沒有手動解開的話检访,10秒鐘后將會自動解開
lock.lock(10, TimeUnit.SECONDS);

// 為加鎖等待100秒時間,并在加鎖成功10秒鐘后自動解開
boolean res = lock.tryLock(100, 10, TimeUnit.SECONDS);
...
lock.unlock();

8.4. 紅鎖(RedLock)

基于Redis的Redisson紅鎖RedissonRedLock對象實現(xiàn)了Redlock介紹的加鎖算法仔掸。該對象也可以用來將多個RLock對象關(guān)聯(lián)為一個紅鎖脆贵,每個RLock對象實例可以來自于不同的Redisson實例。

RLock lock1 = redissonInstance1.getLock("lock1");
RLock lock2 = redissonInstance2.getLock("lock2");
RLock lock3 = redissonInstance3.getLock("lock3");

RedissonRedLock lock = new RedissonRedLock(lock1, lock2, lock3);
// 同時加鎖:lock1 lock2 lock3
// 紅鎖在大部分節(jié)點上加鎖成功就算成功起暮。
lock.lock();
...
lock.unlock();

大家都知道卖氨,如果負責儲存某些分布式鎖的某些Redis節(jié)點宕機以后,而且這些鎖正好處于鎖住的狀態(tài)時负懦,這些鎖會出現(xiàn)鎖死的狀態(tài)筒捺。為了避免這種情況的發(fā)生,Redisson內(nèi)部提供了一個監(jiān)控鎖的看門狗纸厉,它的作用是在Redisson實例被關(guān)閉前系吭,不斷的延長鎖的有效期。默認情況下颗品,看門狗的檢查鎖的超時時間是30秒鐘村斟,也可以通過修改Config.lockWatchdogTimeout來另行指定。

另外Redisson還通過加鎖的方法提供了leaseTime的參數(shù)來指定加鎖的時間抛猫。超過這個時間后鎖便自動解開了。

RedissonRedLock lock = new RedissonRedLock(lock1, lock2, lock3);
// 給lock1孩灯,lock2闺金,lock3加鎖,如果沒有手動解開的話峰档,10秒鐘后將會自動解開
lock.lock(10, TimeUnit.SECONDS);

// 為加鎖等待100秒時間败匹,并在加鎖成功10秒鐘后自動解開
boolean res = lock.tryLock(100, 10, TimeUnit.SECONDS);
...
lock.unlock();

8.5. 讀寫鎖(ReadWriteLock)

基于Redis的Redisson分布式可重入讀寫鎖RReadWriteLock Java對象實現(xiàn)了java.util.concurrent.locks.ReadWriteLock接口。同時還支持自動過期解鎖讥巡。該對象允許同時有多個讀取鎖掀亩,但是最多只能有一個寫入鎖。

RReadWriteLock rwlock = redisson.getLock("anyRWLock");
// 最常見的使用方法
rwlock.readLock().lock();
// 或
rwlock.writeLock().lock();

大家都知道欢顷,如果負責儲存這個分布式鎖的Redis節(jié)點宕機以后槽棍,而且這個鎖正好處于鎖住的狀態(tài)時,這個鎖會出現(xiàn)鎖死的狀態(tài)。為了避免這種情況的發(fā)生炼七,Redisson內(nèi)部提供了一個監(jiān)控鎖的看門狗缆巧,它的作用是在Redisson實例被關(guān)閉前,不斷的延長鎖的有效期豌拙。默認情況下陕悬,看門狗的檢查鎖的超時時間是30秒鐘,也可以通過修改Config.lockWatchdogTimeout來另行指定按傅。

另外Redisson還通過加鎖的方法提供了leaseTime的參數(shù)來指定加鎖的時間捉超。超過這個時間后鎖便自動解開了。

// 10秒鐘以后自動解鎖
// 無需調(diào)用unlock方法手動解鎖
rwlock.readLock().lock(10, TimeUnit.SECONDS);
// 或
rwlock.writeLock().lock(10, TimeUnit.SECONDS);

// 嘗試加鎖唯绍,最多等待100秒拼岳,上鎖以后10秒自動解鎖
boolean res = rwlock.readLock().tryLock(100, 10, TimeUnit.SECONDS);
// 或
boolean res = rwlock.writeLock().tryLock(100, 10, TimeUnit.SECONDS);
...
lock.unlock();

8.6. 信號量(Semaphore)

基于Redis的Redisson的分布式信號量(Semaphore)Java對象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();

8.7. 可過期性信號量(PermitExpirableSemaphore)

基于Redis的Redisson可過期性信號量(PermitExpirableSemaphore)是在RSemaphore對象的基礎上推捐,為每個信號增加了一個過期時間裂问。每個信號可以通過獨立的ID來辨識,釋放時只能通過提交這個ID才能釋放牛柒。

RPermitExpirableSemaphore semaphore = redisson.getPermitExpirableSemaphore("mySemaphore");
String permitId = semaphore.acquire();
// 獲取一個信號堪簿,有效期只有2秒鐘。
String permitId = semaphore.acquire(2, TimeUnit.SECONDS);
// ...
semaphore.release(permitId);

8.8. 閉鎖(CountDownLatch)

基于Redisson的Redisson分布式閉鎖(CountDownLatch)Java對象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)容合作請聯(lián)系作者
  • 序言:七十年代末椭更,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子蛾魄,更是在濱河造成了極大的恐慌虑瀑,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,383評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件滴须,死亡現(xiàn)場離奇詭異舌狗,居然都是意外死亡,警方通過查閱死者的電腦和手機扔水,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,522評論 3 385
  • 文/潘曉璐 我一進店門痛侍,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人魔市,你說我怎么就攤上這事主届。” “怎么了待德?”我有些...
    開封第一講書人閱讀 157,852評論 0 348
  • 文/不壞的土叔 我叫張陵君丁,是天一觀的道長。 經(jīng)常有香客問我将宪,道長绘闷,這世上最難降的妖魔是什么橡庞? 我笑而不...
    開封第一講書人閱讀 56,621評論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮簸喂,結(jié)果婚禮上毙死,老公的妹妹穿的比我還像新娘。我一直安慰自己喻鳄,他們只是感情好扼倘,可當我...
    茶點故事閱讀 65,741評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著除呵,像睡著了一般再菊。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上颜曾,一...
    開封第一講書人閱讀 49,929評論 1 290
  • 那天纠拔,我揣著相機與錄音,去河邊找鬼泛豪。 笑死稠诲,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的诡曙。 我是一名探鬼主播臀叙,決...
    沈念sama閱讀 39,076評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼价卤!你這毒婦竟也來了劝萤?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,803評論 0 268
  • 序言:老撾萬榮一對情侶失蹤慎璧,失蹤者是張志新(化名)和其女友劉穎床嫌,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體胸私,經(jīng)...
    沈念sama閱讀 44,265評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡厌处,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,582評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了岁疼。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片嘱蛋。...
    茶點故事閱讀 38,716評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖五续,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情龄恋,我是刑警寧澤疙驾,帶...
    沈念sama閱讀 34,395評論 4 333
  • 正文 年R本政府宣布,位于F島的核電站郭毕,受9級特大地震影響它碎,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 40,039評論 3 316
  • 文/蒙蒙 一扳肛、第九天 我趴在偏房一處隱蔽的房頂上張望傻挂。 院中可真熱鬧,春花似錦挖息、人聲如沸金拒。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,798評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽绪抛。三九已至,卻和暖如春电禀,著一層夾襖步出監(jiān)牢的瞬間幢码,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,027評論 1 266
  • 我被黑心中介騙來泰國打工尖飞, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留症副,地道東北人。 一個月前我還...
    沈念sama閱讀 46,488評論 2 361
  • 正文 我出身青樓政基,卻偏偏與公主長得像贞铣,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子腋么,可洞房花燭夜當晚...
    茶點故事閱讀 43,612評論 2 350

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