Springboot基于Redisson實(shí)現(xiàn)Redis分布式可重入鎖源碼解析
一、前言
我們?cè)趯?shí)現(xiàn)使用Redis實(shí)現(xiàn)分布式鎖,最開始一般使用SET resource-name anystring NX EX max-lock-time
進(jìn)行加鎖,使用Lua腳本保證原子性進(jìn)行實(shí)現(xiàn)釋放鎖。這樣手動(dòng)實(shí)現(xiàn)比較麻煩,對(duì)此Redis官網(wǎng)也明確說Java版使用Redisson
來(lái)實(shí)現(xiàn)。小編也是看了官網(wǎng)慢慢的摸索清楚,特寫此記錄一下。從官網(wǎng)到整合Springboot到源碼解讀,以單節(jié)點(diǎn)為例,小編的理解都在注釋里,希望可以幫助到大家!!
二、為什么使用Redisson
1. 我們打開官網(wǎng)
2. 我們可以看到官方讓我們?nèi)ナ褂闷渌?/h3>
3. 打開官方推薦
4. 找到文檔
5. Redisson結(jié)構(gòu)
三、Springboot整合Redisson
1. 導(dǎo)入依賴
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> </dependency> <!--redis分布式鎖--> <dependency> <groupId>org.redisson</groupId> <artifactId>redisson</artifactId> <version>3.12.0</version> </dependency>
2. 以官網(wǎng)為例查看如何配置
3. 編寫配置類
import org.redisson.Redisson; import org.redisson.api.RedissonClient; import org.redisson.config.Config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** * @author wangzhenjun * @date 2022/2/9 9:57 */ @Configuration public class MyRedissonConfig { /** * 所有對(duì)redisson的使用都是通過RedissonClient來(lái)操作的 * @return */ @Bean(destroyMethod="shutdown") public RedissonClient redisson(){ // 1. 創(chuàng)建配置 Config config = new Config(); // 一定要加redis:// config.useSingleServer().setAddress("redis://192.168.17.130:6379"); // 2. 根據(jù)config創(chuàng)建出redissonClient實(shí)例 RedissonClient redissonClient = Redisson.create(config); return redissonClient; } }
4. 官網(wǎng)測(cè)試加鎖例子
5. 根據(jù)官網(wǎng)簡(jiǎn)單Controller接口編寫
@ResponseBody @GetMapping("/hello") public String hello(){ // 1.獲取一把鎖,只要鎖名字一樣,就是同一把鎖 RLock lock = redisson.getLock("my-lock"); // 2. 加鎖 lock.lock();// 阻塞試等待 默認(rèn)加的都是30s // 帶參數(shù)情況 // lock.lock(10, TimeUnit.SECONDS);// 10s自動(dòng)解鎖,自動(dòng)解鎖時(shí)間一定要大于業(yè)務(wù)的執(zhí)行時(shí)間。 try { System.out.println("加鎖成功" + Thread.currentThread().getId()); Thread.sleep(30000); } catch (InterruptedException e) { e.printStackTrace(); } finally { // 3. 解鎖 System.out.println("解鎖成功:" + Thread.currentThread().getId()); lock.unlock(); } return "hello"; }
6. 測(cè)試
四、lock.lock()源碼分析
1. 打開RedissonLock實(shí)現(xiàn)類
2. 找到實(shí)現(xiàn)方法
@Override public void lock() { try { // 我們發(fā)現(xiàn)不穿過期時(shí)間源碼默認(rèn)過期時(shí)間為-1 lock(-1, null, false); } catch (InterruptedException e) { throw new IllegalStateException(); } }
3. 按住Ctrl進(jìn)去lock方法
private void lock(long leaseTime, TimeUnit unit, boolean interruptibly) throws InterruptedException { // 獲取線程的id,占有鎖的時(shí)候field的值為UUID:線程號(hào)id long threadId = Thread.currentThread().getId(); // 嘗試獲得鎖 Long ttl = tryAcquire(leaseTime, unit, threadId); // lock acquired 獲得鎖,返回 if (ttl == null) { return; } // 這里說明獲取鎖失敗,就通過線程id訂閱這個(gè)鎖 RFuture<RedissonLockEntry> future = subscribe(threadId); if (interruptibly) { commandExecutor.syncSubscriptionInterrupted(future); } else { commandExecutor.syncSubscription(future); } try { // 這里進(jìn)行自旋,不斷嘗試獲取鎖 while (true) { // 繼續(xù)嘗試獲取鎖 ttl = tryAcquire(leaseTime, unit, threadId); // lock acquired 獲取成功 if (ttl == null) { // 直接返回,挑出自旋 break; } // waiting for message 繼續(xù)等待獲得鎖 if (ttl >= 0) { try { future.getNow().getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS); } catch (InterruptedException e) { if (interruptibly) { throw e; } future.getNow().getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS); } } else { if (interruptibly) { future.getNow().getLatch().acquire(); } else { future.getNow().getLatch().acquireUninterruptibly(); } } } } finally { // 取消訂閱 unsubscribe(future, threadId); } // get(lockAsync(leaseTime, unit)); }
4. 進(jìn)去嘗試獲取鎖方法
private Long tryAcquire(long leaseTime, TimeUnit unit, long threadId) { // 直接進(jìn)入異步方法 return get(tryAcquireAsync(leaseTime, unit, threadId)); } private <T> RFuture<Long> tryAcquireAsync(long leaseTime, TimeUnit unit, long threadId) { // 這里進(jìn)行判斷如果沒有設(shè)置參數(shù)leaseTime = -1 if (leaseTime != -1) { return tryLockInnerAsync(leaseTime, unit, threadId, RedisCommands.EVAL_LONG); } // 此方法進(jìn)行獲得鎖,過期時(shí)間為看門狗的默認(rèn)時(shí)間 // private long lockWatchdogTimeout = 30 * 1000;看門狗默認(rèn)過期時(shí)間為30s // 加鎖和過期時(shí)間要保證原子性,這個(gè)方法后面肯定調(diào)用執(zhí)行了Lua腳本,我們下面在看 RFuture<Long> ttlRemainingFuture = tryLockInnerAsync(commandExecutor.getConnectionManager().getCfg().getLockWatchdogTimeout(), TimeUnit.MILLISECONDS, threadId, RedisCommands.EVAL_LONG); // 開啟一個(gè)定時(shí)任務(wù)進(jìn)行不斷刷新過期時(shí)間 ttlRemainingFuture.onComplete((ttlRemaining, e) -> { if (e != null) { return; } // lock acquired 獲得鎖 if (ttlRemaining == null) { // 刷新過期時(shí)間方法,我們下一步詳細(xì)說一下 scheduleExpirationRenewal(threadId); }); return ttlRemainingFuture;
5. 查看tryLockInnerAsync()方法
<T> RFuture<T> tryLockInnerAsync(long leaseTime, TimeUnit unit, long threadId, RedisStrictCommand<T> command) { internalLockLeaseTime = unit.toMillis(leaseTime); return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, command, // 首先判斷鎖是否存在 "if (redis.call('exists', KEYS[1]) == 0) then " + // 存在則獲取鎖 "redis.call('hset', KEYS[1], ARGV[2], 1); " + // 然后設(shè)置過期時(shí)間 "redis.call('pexpire', KEYS[1], ARGV[1]); " + "return nil; " + "end; " + // hexists查看哈希表的指定字段是否存在,存在鎖并且是當(dāng)前線程持有鎖 "if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then " + // hincrby自增一 "redis.call('hincrby', KEYS[1], ARGV[2], 1); " + // 鎖的值大于1,說明是可重入鎖,重置過期時(shí)間 "redis.call('pexpire', KEYS[1], ARGV[1]); " + "return nil; " + "end; " + // 鎖已存在,且不是本線程,則返回過期時(shí)間ttl "return redis.call('pttl', KEYS[1]);", Collections.<Object>singletonList(getName()), internalLockLeaseTime, getLockName(threadId)); }
6. 進(jìn)入4留下的定時(shí)任務(wù)scheduleExpirationRenewal()方法
一步步往下找源碼:scheduleExpirationRenewal --->renewExpiration
根據(jù)下面源碼,定時(shí)任務(wù)刷新時(shí)間為:internalLockLeaseTime / 3,是看門狗的1/3,即為10s刷新一次
private void renewExpiration() { ExpirationEntry ee = EXPIRATION_RENEWAL_MAP.get(getEntryName()); if (ee == null) { return; } Timeout task = commandExecutor.getConnectionManager().newTimeout(new TimerTask() { @Override public void run(Timeout timeout) throws Exception { ExpirationEntry ent = EXPIRATION_RENEWAL_MAP.get(getEntryName()); if (ent == null) { return; } Long threadId = ent.getFirstThreadId(); if (threadId == null) { return; } RFuture<Boolean> future = renewExpirationAsync(threadId); future.onComplete((res, e) -> { if (e != null) { log.error("Can't update lock " + getName() + " expiration", e); return; } if (res) { // reschedule itself renewExpiration(); } }); } }, internalLockLeaseTime / 3, TimeUnit.MILLISECONDS); ee.setTimeout(task); }
五、lock.lock(10, TimeUnit.SECONDS)源碼分析
1. 打開實(shí)現(xiàn)類
@Override public void lock(long leaseTime, TimeUnit unit) { try { // 這里的過期時(shí)間為我們輸入的10 lock(leaseTime, unit, false); } catch (InterruptedException e) { throw new IllegalStateException(); } }
2. 方法lock()
實(shí)現(xiàn)展示,同三.3源碼
3. 直接來(lái)到嘗試獲得鎖tryAcquireAsync()
方法
private <T> RFuture<Long> tryAcquireAsync(long leaseTime, TimeUnit unit, long threadId) { // 這里進(jìn)行判斷如果沒有設(shè)置參數(shù)leaseTime = -1,此時(shí)我們?yōu)?0 if (leaseTime != -1) { // 來(lái)到此方法 return tryLockInnerAsync(leaseTime, unit, threadId, RedisCommands.EVAL_LONG); } // 此處省略后面內(nèi)容,前面以詳細(xì)說明。。。。 }
4. 打開tryLockInnerAsync()
方法
我們不難發(fā)現(xiàn)和沒有傳過期時(shí)間的方法一樣,只不過leaseTime的值變了。
<T> RFuture<T> tryLockInnerAsync(long leaseTime, TimeUnit unit, long threadId, RedisStrictCommand<T> command) { internalLockLeaseTime = unit.toMillis(leaseTime); return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, command, // 首先判斷鎖是否存在 "if (redis.call('exists', KEYS[1]) == 0) then " + // 存在則獲取鎖 "redis.call('hset', KEYS[1], ARGV[2], 1); " + // 然后設(shè)置過期時(shí)間 "redis.call('pexpire', KEYS[1], ARGV[1]); " + "return nil; " + "end; " + // hexists查看哈希表的指定字段是否存在,存在鎖并且是當(dāng)前線程持有鎖 "if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then " + // hincrby自增一 "redis.call('hincrby', KEYS[1], ARGV[2], 1); " + // 鎖的值大于1,說明是可重入鎖,重置過期時(shí)間 "redis.call('pexpire', KEYS[1], ARGV[1]); " + "return nil; " + "end; " + // 鎖已存在,且不是本線程,則返回過期時(shí)間ttl "return redis.call('pttl', KEYS[1]);", Collections.<Object>singletonList(getName()), internalLockLeaseTime, getLockName(threadId)); }
六、lock.unlock()源碼分析
1. 打開方法實(shí)現(xiàn)
@Override public void unlock() { try { // 點(diǎn)擊進(jìn)入釋放鎖方法 get(unlockAsync(Thread.currentThread().getId())); } catch (RedisException e) { if (e.getCause() instanceof IllegalMonitorStateException) { throw (IllegalMonitorStateException) e.getCause(); } else { throw e; } } // Future<Void> future = unlockAsync(); // future.awaitUninterruptibly(); // if (future.isSuccess()) { // return; // } // if (future.cause() instanceof IllegalMonitorStateException) { // throw (IllegalMonitorStateException)future.cause(); // } // throw commandExecutor.convertException(future); }
2. 打開unlockAsync()
方法
@Override public RFuture<Void> unlockAsync(long threadId) { RPromise<Void> result = new RedissonPromise<Void>(); // 解鎖方法,后面展開說 RFuture<Boolean> future = unlockInnerAsync(threadId); // 完成 future.onComplete((opStatus, e) -> { if (e != null) { // 取消到期續(xù)訂 cancelExpirationRenewal(threadId); // 將這個(gè)未來(lái)標(biāo)記為失敗并通知所有人 result.tryFailure(e); return; } // 狀態(tài)為空,說明解鎖的線程和當(dāng)前鎖不是同一個(gè)線程 if (opStatus == null) { IllegalMonitorStateException cause = new IllegalMonitorStateException("attempt to unlock lock, not locked by current thread by node id: " + id + " thread-id: " + threadId); result.tryFailure(cause); return; } cancelExpirationRenewal(threadId); result.trySuccess(null); }); return result; }
3. 打開unlockInnerAsync()
方法
protected RFuture<Boolean> unlockInnerAsync(long threadId) { return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN, // 判斷釋放鎖的線程和已存在鎖的線程是不是同一個(gè)線程,不是返回空 "if (redis.call('hexists', KEYS[1], ARGV[3]) == 0) then " + "return nil;" + "end; " + // 釋放鎖后,加鎖次數(shù)減一 "local counter = redis.call('hincrby', KEYS[1], ARGV[3], -1); " + // 判斷剩余數(shù)量是否大于0 "if (counter > 0) then " + // 大于0 ,則刷新過期時(shí)間 "redis.call('pexpire', KEYS[1], ARGV[2]); " + "return 0; " + "else " + // 釋放鎖,刪除key并發(fā)布鎖釋放的消息 "redis.call('del', KEYS[1]); " + "redis.call('publish', KEYS[2], ARGV[1]); " + "return 1; "+ "end; " + "return nil;", Arrays.<Object>asList(getName(), getChannelName()), LockPubSub.UNLOCK_MESSAGE, internalLockLeaseTime, getLockName(threadId)); }
七、總結(jié)
這樣大家就跟著小編走完了一遍底層源碼,是不是感覺自己又行了,哈哈哈。小編走下來(lái)一遍覺得收貨還是蠻大的,以前不敢點(diǎn)進(jìn)去源碼,進(jìn)去就懵逼了,所以人要大膽的向前邁出第一步。
到此這篇關(guān)于Springboot基于Redisson實(shí)現(xiàn)Redis分布式可重入鎖【案例到源碼分析】的文章就介紹到這了,更多相關(guān)SpringbootRedis分布式可重入鎖內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Spring MVC中使用Google kaptcha驗(yàn)證碼的方法詳解
kaptcha 是一個(gè)非常實(shí)用的驗(yàn)證碼生成工具。有了它,你可以生成各種樣式的驗(yàn)證碼,因?yàn)樗强膳渲玫?,下面這篇文章主要給大家介紹了關(guān)于Spring MVC中使用Google kaptcha驗(yàn)證碼的方法,需要的朋友可以參考借鑒,下面來(lái)一起看看吧。2017-10-10Java利用Poi讀取excel并對(duì)所有類型進(jìn)行處理
這篇文章主要為大家詳細(xì)介紹了Java利用Poi讀取excel并對(duì)所有類型進(jìn)行處理的相關(guān)知識(shí),文中的示例代碼講解詳細(xì),感興趣的小伙伴可以了解一下2024-01-01Spring Security Remember me使用及原理詳解
這篇文章主要介紹了Spring Security Remember me使用及原理詳解,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-09-09Java synchronized線程交替運(yùn)行實(shí)現(xiàn)過程詳解
這篇文章主要介紹了Java synchronized線程交替運(yùn)行實(shí)現(xiàn)過程詳解,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-11-11Java 騰訊驗(yàn)證碼平臺(tái)使用實(shí)例
這篇文章主要介紹了Java 騰訊驗(yàn)證碼平臺(tái)使用實(shí)例,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-02-02