Redisson分布式鎖實(shí)現(xiàn)
Redisson分布式鎖實(shí)現(xiàn)
多線程下的數(shù)據(jù)一致性問(wèn)題一直都是熱點(diǎn)問(wèn)題,既要考慮到數(shù)據(jù)的一致,又要考慮實(shí)現(xiàn)的效率,在分布式情況下,這又要成為一種新的難題。分布式鎖和我們java基礎(chǔ)中學(xué)習(xí)到的synchronized略有不同,synchronized中我們的鎖是個(gè)對(duì)象,當(dāng)前系統(tǒng)部署在不同的服務(wù)實(shí)例上,單純使用synchronized或者lock 已經(jīng)無(wú)法滿足對(duì)庫(kù)存一致性的判斷。本次主要講解基于rediss 實(shí)現(xiàn)的分布式鎖、
基本用法
<dependency>
? ?<groupId>org.redisson</groupId>
? ?<artifactId>redisson</artifactId>
? ?<version>3.8.2</version>
</dependency>Config config = new Config();
config.useClusterServers()
? ?.setScanInterval(2000) // cluster state scan interval in milliseconds
? ?.addNodeAddress("redis://127.0.0.1:7000", "redis://127.0.0.1:7001")
? ?.addNodeAddress("redis://127.0.0.1:7002");
RedissonClient redisson = Redisson.create(config);
1. 可重入鎖(Reentrant Lock)
Redisson的分布式可重入鎖RLock Java對(duì)象實(shí)現(xiàn)了java.util.concurrent.locks.Lock接口,同時(shí)還支持自動(dòng)過(guò)期解鎖。

public void testReentrantLock(RedissonClient redisson){ ? ? ? ?RLock lock = redisson.getLock("anyLock"); ? ? ? ?try{ ? ? ? ? ? ?// 1. 最常見(jiàn)的使用方法 ? ? ? ? ? ?//lock.lock(); ? ? ? ? ? ?// 2. 支持過(guò)期解鎖功能,10秒鐘以后自動(dòng)解鎖, 無(wú)需調(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(); ? ? ? ?} ? ?}

2. 公平鎖(Fair Lock)
Redisson分布式可重入公平鎖也是實(shí)現(xiàn)了java.util.concurrent.locks.Lock接口的一種RLock對(duì)象。在提供了自動(dòng)過(guò)期解鎖功能的同時(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{ ? ? ? ? ? ?// 最常見(jiàn)的使用方法 ? ? ? ? ? ?fairLock.lock(); ? ? ? ? ? ?// 支持過(guò)期解鎖功能, 10秒鐘以后自動(dòng)解鎖,無(wú)需調(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);
3. 聯(lián)鎖(MultiLock)
Redisson的RedissonMultiLock對(duì)象可以將多個(gè)RLock對(duì)象關(guān)聯(lián)為一個(gè)聯(lián)鎖,每個(gè)RLock對(duì)象實(shí)例可以來(lái)自于不同的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ì)象也可以用來(lái)將多個(gè)RLock
對(duì)象關(guān)聯(lián)為一個(gè)紅鎖,每個(gè)RLock對(duì)象實(shí)例可以來(lái)自于不同的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(); ? ? ? ?} ? ?}

5. 讀寫(xiě)鎖(ReadWriteLock)
Redisson的分布式可重入讀寫(xiě)鎖RReadWriteLock Java對(duì)象實(shí)現(xiàn)了java.util.concurrent.locks.ReadWriteLock接口。同時(shí)還支持自動(dòng)過(guò)期解鎖。該對(duì)象允許同時(shí)有多個(gè)讀取鎖,但是最多只能有一個(gè)寫(xiě)入鎖。

RReadWriteLock rwlock = redisson.getLock("anyRWLock");// 最常見(jiàn)的使用方法rwlock.readLock().lock();// 或rwlock.writeLock().lock();// 支持過(guò)期解鎖功能// 10秒鐘以后自動(dòng)解鎖// 無(wú)需調(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();

6. 信號(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();

7. 可過(guò)期性信號(hào)量(PermitExpirableSemaphore)
Redisson的可過(guò)期性信號(hào)量(PermitExpirableSemaphore)實(shí)在RSemaphore對(duì)象的基礎(chǔ)上,為每個(gè)信號(hào)增加了一個(gè)過(guò)期時(shí)間。每個(gè)信號(hào)可以通過(guò)獨(dú)立的ID來(lái)辨識(shí),釋放時(shí)只能通過(guò)提交這個(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);
8. 閉鎖(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();

?
加鎖有如下注意事項(xiàng):
加鎖需要設(shè)置超時(shí)時(shí)間,防止出現(xiàn)死鎖
加鎖以及設(shè)置超時(shí)時(shí)間的時(shí)候,需要保證兩個(gè)操作的原子性,因而最好使用lua腳本或者使用支持NX以及EX的set方法
加鎖的時(shí)候需要把加鎖的調(diào)用方信息,比如線程id給記錄下來(lái),這個(gè)在解鎖的時(shí)候需要使用
對(duì)于加鎖時(shí)長(zhǎng)不確定的任務(wù),為防止任務(wù)未執(zhí)行完導(dǎo)致超時(shí)被釋放,需要對(duì)尚未運(yùn)行完的任務(wù)延長(zhǎng)失效時(shí)間
解鎖有如下注意事項(xiàng):
解鎖一系列操作(
判斷key是否存在,存在的話刪除key等
)需要保證原子性,因而最好使用lua腳本解鎖需要判斷調(diào)用方是否與加鎖時(shí)記錄的是否一致,防止鎖被誤刪
如果有延續(xù)失效時(shí)間的延時(shí)任務(wù),在解鎖的時(shí)候,需要終止掉該任務(wù)