簡單使用
- 基本
// 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();