一琅拌、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ù)。
三靴寂、配置
- 單機(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);
}
}
四磷蜀、分布式鎖
- 可重入鎖(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();
}
}
- 公平鎖(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);
- 聯(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();
}
}
- 讀寫鎖(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();
- 信號(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();
- 可過期性信號(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);
- 閉鎖(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();