1. 程式人生 > >分散式鎖-Redission-Lock鎖的使用與原理

分散式鎖-Redission-Lock鎖的使用與原理

# 環境準備 新增 Maven 依賴 ```xml org.redisson redisson 3.12.0 ``` 新增配置類 ```java @Configuration public class MyRedissonConfig { @Bean(destroyMethod = "shutdown") RedissonClient redisson() throws IOException { Config config = new Config(); config.useSingleServer().setAddress("redis://192.168.56.10:6379"); return Redisson.create(config); } } ``` 基本使用程式碼如下: ```java @GetMapping("/hello") @ResponseBody public String hello() { //獲取Lock鎖,設定鎖的名稱 RLock lock = redisson.getLock("my-lock"); //開啟 lock.lock(); try { System.out.println("上鎖:" + Thread.currentThread().getId()); //模擬業務處理20秒 TimeUnit.SECONDS.sleep(20); } catch (InterruptedException e) { e.printStackTrace(); }finally { System.out.println("解鎖:" + Thread.currentThread().getId()); //釋放 lock.unlock(); } return "hello"; } ``` # 分析 當我們傳送 `/hello` 請求後等待 20 秒得到響應結果,會在 Redis 中儲存鎖的資訊(如下圖所示),期間,其它使用者傳送 `/hello` 請求時會被阻塞,只有前一個請求結束後釋放鎖,當前請求才會進入。 ![](https://gitee.com/songjilong/FigureBed/raw/master/img/20200514203408.png) **思考1:如果在業務處理過程中程式突然終止,鎖沒有得到釋放,是否會一直阻塞下去?** 經過實驗,在業務處理的20秒中,將服務手動停止,重新整理 Redis 中 my-lock 的資訊,發現 TTL 不斷的減小,直到失效,傳送其它請求能夠正常執行,這說明,即使不釋放鎖,Redis 設定的過期時間到了也會自動刪除鎖的資訊。 ```java //獲取當前執行緒id long threadId = Thread.currentThread().getId(); //獲取此執行緒的鎖 Long ttl = tryAcquire(leaseTime, unit, threadId); //如果獲取不到,則說明鎖已經釋放了,直接返回 if (ttl == null) { return; } while (true) { ttl = tryAcquire(leaseTime, unit, threadId); //判斷是否能獲取到鎖 if (ttl == null) { break; } ... } ``` **思考2:過期時間是多少?如果我們的業務處理時間超過了過期時間,豈不是還沒處理完就把鎖的資訊給刪了?** 正常啟動服務訪問 `/hello`,重新整理 my-lock 的資訊,我們發現,TTL 每次減少到 20 就再次變為 30,直到業務處理完成,my-lock 被刪除。查詢相關原始碼如下: ```java while (true) { //嘗試獲取鎖 ttl = tryAcquire(leaseTime, unit, threadId); //如果獲取不到,說明執行該執行緒執行結束,就終止迴圈 if (ttl == null) { break; } //如果獲取到了就繼續迴圈 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(); } } } ``` 繼續深入原始碼可以看到,如果不指定鎖的時間,就預設為 30 秒,它有一個好聽的名字:看門狗 ```java private long lockWatchdogTimeout = 30 * 1000; ``` 只要佔領鎖,就會啟動一個定時任務:每隔一段時間重新給鎖設定過期時間 ```java protected RFuture renewExpirationAsync(long threadId) { return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN, "if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then " + "redis.call('pexpire', KEYS[1], ARGV[1]); " + "return 1; " + "end; " + "return 0;", Collections.singletonList(getName()), internalLockLeaseTime, getLockName(threadId)); //internalLockLeaseTime就是看門狗的時間 } ``` 每隔多長久重新整理一下呢? ```java //獲取看門狗的時間,賦值給自己 this.internalLockLeaseTime = xxx.getLockWatchdogTimeout(); public long getLockWatchdogTimeout() { return lockWatchdogTimeout; } Timeout task = commandExecutor.getConnectionManager().newTimeout(new TimerTask() { @Override public void run(Timeout timeout) throws Exception { ... } //使用的時候除3,也就是10秒重新整理一次 }, internalLockLeaseTime / 3, TimeUnit.MILLISECONDS); ``` **思考三:如何自定義過期時間?** lock() 方法還有一個過載方法,可以傳入過期時間和單位 ```java void lock(long leaseTime, TimeUnit unit); ``` 我們將之前的程式碼修改,設定為 15 秒,重啟服務再測試 ```java lock.lock(15, TimeUnit.SECONDS); ``` 訪問 `/hello`,重新整理 Redis 中 my-lock 的資訊會發現,TTL 從 15 減到 0,然後鎖資訊過期,並不會出現之前的 10秒一重新整理,檢視原始碼會發現: ```java private RFuture tryAcquireAsync(long leaseTime, TimeUnit unit, long threadId) { //如果傳入了過期時間,則直接執行tryLockInnerAsync裡面的Lua指令碼 if (leaseTime != -1) { return tryLockInnerAsync(leaseTime, unit, threadId, RedisCommands.EVAL_LONG); } //沒有傳入過期時間,執行下面的邏輯 RFuture ttlRemainingFuture = tryLockInnerAsync(commandExecutor.getConnectionManager().getCfg().getLockWatchdogTimeout(), TimeUnit.MILLISECONDS, threadId, RedisCommands.EVAL_LONG); ttlRemainingFuture.onComplete((ttlRemaining, e) -> { //有異常,直接返回 if (e != null) { return; } if (ttlRemaining == null) { //重新整理過期時間 scheduleExpirationRenewal(threadId); } }); return ttlRemainingFuture; } ``` # 總結 1、lock 鎖是執行緒阻塞的 2、使用 lock 的無參方法,鎖的預設時間是 30 秒,並且會每隔 10 秒重新整理為 30 秒,只要業務沒執行完,就會一直續期,如果執行完成或者突然中止,則不會再續期,達到過期時間就釋放鎖 3、使用 lock 的有參方法指定時間,到達指定時間會自動解鎖,因此設定的時間必須大於業務執行時間,否則,業務沒執行完,鎖就會被釋放 4、推薦使用指定時間的方式,省掉了續期操作,但需要合理設定過期時間,不能過早的使