redis desktop manager_面试官:Redis分布式锁如何解决锁超时问题?
Java面試筆試面經、Java技術每天學習一點
Java面試
關注不迷路
作者:wangzaiplus
來源:https://www.jianshu.com/u/8cb4591440ca
一、前言
關于redis分布式鎖, 查了很多資料, 發現很多只是實現了最基礎的功能, 但是, 并沒有解決當鎖已超時而業務邏輯還未執行完的問題, 這樣會導致: A線程超時時間設為10s(為了解決死鎖問題), 但代碼執行時間可能需要30s, 然后redis服務端10s后將鎖刪除, 此時, B線程恰好申請鎖, redis服務端不存在該鎖, 可以申請, 也執行了代碼, 那么問題來了, A、B線程都同時獲取到鎖并執行業務邏輯, 這與分布式鎖最基本的性質相違背: 在任意一個時刻, 只有一個客戶端持有鎖, 即獨享
為了解決這個問題, 本文將用完整的代碼和測試用例進行驗證, 希望能給小伙伴帶來一點幫助
二、準備工作
壓測工具jmeter
https://pan.baidu.com/share/init?surl=NN0c0tDYQjBTTPA-WTT3yg
提取碼: 8f2a
redis-desktop-manager客戶端
https://pan.baidu.com/share/init?surl=NoJtZZZOXsk45aQYtveWbQ
提取碼: 9bhf
postman
https://pan.baidu.com/share/init?surl=28sGJk4zxoOknAd-47hE7w
提取碼: vfu7
也可以直接官網下載, 我這邊都整理到網盤了
需要postman是因為我還沒找到jmeter多開窗口的辦法, 哈哈
三、說明
1、springmvc項目
2、maven依賴
????????????????<dependency>
????????????<groupId>org.springframework.datagroupId>
????????????<artifactId>spring-data-redisartifactId>
????????????<version>1.6.5.RELEASEversion>
????????dependency>
????????<dependency>
????????????<groupId>redis.clientsgroupId>
????????????<artifactId>jedisartifactId>
????????????<version>2.7.3version>
????????dependency>
3、核心類
分布式鎖工具類: DistributedLock
測試接口類: PcInformationServiceImpl
鎖延時守護線程類: PostponeTask
四、實現思路
先測試在不開啟鎖延時線程的情況下, A線程超時時間設為10s, 執行業務邏輯時間設為30s, 10s后, 調用接口, 查看是否能夠獲取到鎖, 如果獲取到, 說明存在線程安全性問題
同上, 在加鎖的同時, 開啟鎖延時線程, 調用接口, 查看是否能夠獲取到鎖, 如果獲取不到, 說明延時成功, 安全性問題解決
五、實現
1、版本01代碼
1)、DistributedLock
package?com.cn.pinliang.common.util;import?com.cn.pinliang.common.thread.PostponeTask;
import?com.google.common.collect.Lists;
import?org.springframework.beans.factory.annotation.Autowired;
import?org.springframework.data.redis.core.RedisCallback;
import?org.springframework.data.redis.core.RedisTemplate;
import?org.springframework.stereotype.Component;
import?redis.clients.jedis.Jedis;
import?java.io.Serializable;
import?java.util.Collections;
@Component
public?class?DistributedLock?{
????@Autowired
????private?RedisTemplate?redisTemplate;private?static?final?Long?RELEASE_SUCCESS?=?1L;private?static?final?String?LOCK_SUCCESS?=?"OK";private?static?final?String?SET_IF_NOT_EXIST?=?"NX";private?static?final?String?SET_WITH_EXPIRE_TIME?=?"EX";//?解鎖腳本(lua)private?static?final?String?RELEASE_LOCK_SCRIPT?=?"if?redis.call('get',?KEYS[1])?==?ARGV[1]?then?return?redis.call('del',?KEYS[1])?else?return?0?end";/**
?????*?分布式鎖
?????*?@param?key
?????*?@param?value
?????*?@param?expireTime?單位:?秒
?????*?@return
?????*/public?boolean?lock(String?key,?String?value,?long?expireTime)?{return?redisTemplate.execute((RedisCallback<Boolean>)?redisConnection?->?{
????????????Jedis?jedis?=?(Jedis)?redisConnection.getNativeConnection();
????????????String?result?=?jedis.set(key,?value,?SET_IF_NOT_EXIST,?SET_WITH_EXPIRE_TIME,?expireTime);if?(LOCK_SUCCESS.equals(result))?{return?Boolean.TRUE;
????????????}return?Boolean.FALSE;
????????});
????}/**
?????*?解鎖
?????*?@param?key
?????*?@param?value
?????*?@return
?????*/public?Boolean?unLock(String?key,?String?value)?{return?redisTemplate.execute((RedisCallback<Boolean>)?redisConnection?->?{
????????????Jedis?jedis?=?(Jedis)?redisConnection.getNativeConnection();
????????????Object?result?=?jedis.eval(RELEASE_LOCK_SCRIPT,?Collections.singletonList(key),?Collections.singletonList(value));if?(RELEASE_SUCCESS.equals(result))?{return?Boolean.TRUE;
????????????}return?Boolean.FALSE;
????????});
????}
}
說明: 就2個方法, 加鎖解鎖, 加鎖使用jedis setnx方法, 解鎖執行lua腳本, 都是原子性操作
2)、PcInformationServiceImpl
????public?JsonResult?add()?throws?Exception?{????????String?key?=?"add_information_lock";
????????String?value?=?RandomUtil.produceStringAndNumber(10);
????????long?expireTime?=?10L;
????????boolean?lock?=?distributedLock.lock(key,?value,?expireTime);
????????String?threadName?=?Thread.currentThread().getName();
????????if?(lock)?{
????????????System.out.println(threadName?+?"?獲得鎖...............................");
????????????Thread.sleep(30000);
????????????distributedLock.unLock(key,?value);
????????????System.out.println(threadName?+?"?解鎖了...............................");
????????}?else?{
????????????System.out.println(threadName?+?"?未獲取到鎖...............................");
????????????return?JsonResult.fail("未獲取到鎖");
????????}
????????return?JsonResult.succeed();
????}
說明: 測試類很簡單, value隨機生成, 保證唯一, 不會在超時情況下解鎖其他客戶端持有的鎖
3)、打開redis-desktop-manager客戶端, 刷新緩存, 可以看到, 此時是沒有add_information_lock的key的
4)、啟動jmeter, 調用接口測試
設置5個線程同時訪問, 在10s的超時時間內查看redis, add_information_lock存在, 多次調接口, 只有一個線程能夠獲取到鎖
redis
1-4個請求, 都未獲取到鎖
第5個請求, 獲取到鎖
OK, 目前為止, 一切正常, 接下來測試10s之后, A仍在執行業務邏輯, 看別的線程是否能獲取到鎖
可以看到, 操作成功, 說明A和B同時執行了這段本應該獨享的代碼, 需要優化。
2、版本02代碼
1)、DistributedLock
package?com.cn.pinliang.common.util;import?com.cn.pinliang.common.thread.PostponeTask;
import?com.google.common.collect.Lists;
import?org.springframework.beans.factory.annotation.Autowired;
import?org.springframework.data.redis.core.RedisCallback;
import?org.springframework.data.redis.core.RedisTemplate;
import?org.springframework.stereotype.Component;
import?redis.clients.jedis.Jedis;
import?java.io.Serializable;
import?java.util.Collections;
@Component
public?class?DistributedLock?{
????@Autowired
????private?RedisTemplate?redisTemplate;private?static?final?Long?RELEASE_SUCCESS?=?1L;private?static?final?Long?POSTPONE_SUCCESS?=?1L;private?static?final?String?LOCK_SUCCESS?=?"OK";private?static?final?String?SET_IF_NOT_EXIST?=?"NX";private?static?final?String?SET_WITH_EXPIRE_TIME?=?"EX";//?解鎖腳本(lua)private?static?final?String?RELEASE_LOCK_SCRIPT?=?"if?redis.call('get',?KEYS[1])?==?ARGV[1]?then?return?redis.call('del',?KEYS[1])?else?return?0?end";//?延時腳本private?static?final?String?POSTPONE_LOCK_SCRIPT?=?"if?redis.call('get',?KEYS[1])?==?ARGV[1]?then?return?redis.call('expire',?KEYS[1],?ARGV[2])?else?return?'0'?end";/**
?????*?分布式鎖
?????*?@param?key
?????*?@param?value
?????*?@param?expireTime?單位:?秒
?????*?@return
?????*/public?boolean?lock(String?key,?String?value,?long?expireTime)?{//?加鎖Boolean?locked?=?redisTemplate.execute((RedisCallback<Boolean>)?redisConnection?->?{
????????????Jedis?jedis?=?(Jedis)?redisConnection.getNativeConnection();
????????????String?result?=?jedis.set(key,?value,?SET_IF_NOT_EXIST,?SET_WITH_EXPIRE_TIME,?expireTime);if?(LOCK_SUCCESS.equals(result))?{return?Boolean.TRUE;
????????????}return?Boolean.FALSE;
????????});if?(locked)?{//?加鎖成功,?啟動一個延時線程,?防止業務邏輯未執行完畢就因鎖超時而使鎖釋放
????????????PostponeTask?postponeTask?=?new?PostponeTask(key,?value,?expireTime,?this);
????????????Thread?thread?=?new?Thread(postponeTask);
????????????thread.setDaemon(Boolean.TRUE);
????????????thread.start();
????????}return?locked;
????}/**
?????*?解鎖
?????*?@param?key
?????*?@param?value
?????*?@return
?????*/public?Boolean?unLock(String?key,?String?value)?{return?redisTemplate.execute((RedisCallback<Boolean>)?redisConnection?->?{
????????????Jedis?jedis?=?(Jedis)?redisConnection.getNativeConnection();
????????????Object?result?=?jedis.eval(RELEASE_LOCK_SCRIPT,?Collections.singletonList(key),?Collections.singletonList(value));if?(RELEASE_SUCCESS.equals(result))?{return?Boolean.TRUE;
????????????}return?Boolean.FALSE;
????????});
????}/**
?????*?鎖延時
?????*?@param?key
?????*?@param?value
?????*?@param?expireTime
?????*?@return
?????*/public?Boolean?postpone(String?key,?String?value,?long?expireTime)?{return?redisTemplate.execute((RedisCallback<Boolean>)?redisConnection?->?{
????????????Jedis?jedis?=?(Jedis)?redisConnection.getNativeConnection();
????????????Object?result?=?jedis.eval(POSTPONE_LOCK_SCRIPT,?Lists.newArrayList(key),?Lists.newArrayList(value,?String.valueOf(expireTime)));if?(POSTPONE_SUCCESS.equals(result))?{return?Boolean.TRUE;
????????????}return?Boolean.FALSE;
????????});
????}
}
說明: 新增了鎖延時方法, lua腳本, 自行腦補相關語法
2)、PcInformationServiceImpl不需要改動
3)、PostponeTask
package?com.cn.pinliang.common.thread;import?com.cn.pinliang.common.util.DistributedLock;
public?class?PostponeTask?implements?Runnable?{
????private?String?key;
????private?String?value;
????private?long?expireTime;
????private?boolean?isRunning;
????private?DistributedLock?distributedLock;
????public?PostponeTask()?{
????}
????public?PostponeTask(String?key,?String?value,?long?expireTime,?DistributedLock?distributedLock)?{
????????this.key?=?key;
????????this.value?=?value;
????????this.expireTime?=?expireTime;
????????this.isRunning?=?Boolean.TRUE;
????????this.distributedLock?=?distributedLock;
????}
????@Overridepublic?void?run()?{
????????long?waitTime?=?expireTime?*?1000?*?2?/?3;//?線程等待多長時間后執行
????????while?(isRunning)?{
????????????try?{
????????????????Thread.sleep(waitTime);
????????????????if?(distributedLock.postpone(key,?value,?expireTime))?{
????????????????????System.out.println("延時成功...........................................................");
????????????????}?else?{
????????????????????this.stop();
????????????????}
????????????}?catch?(Exception?e)?{
????????????????e.printStackTrace();
????????????}
????????}
????}
????private?void?stop()?{
????????this.isRunning?=?Boolean.FALSE;
????}
}
說明: 調用lock同時, 立即開啟PostponeTask線程, 線程等待超時時間的2/3時間后, 開始執行鎖延時代碼, 如果延時成功, add_information_lock這個key會一直存在于redis服務端, 直到業務邏輯執行完畢, 因此在此過程中, 其他線程無法獲取到鎖, 也即保證了線程安全性
下面是測試結果
10s后, 查看redis服務端, add_information_lock仍存在, 說明延時成功
此時用postman再次請求, 發現獲取不到鎖
看一下控制臺打印
A線程在19:09:11獲取到鎖, 在10 * 2 / 3 = 6s后進行延時, 成功, 保證了業務邏輯未執行完畢的情況下不會釋放鎖
A線程執行完畢, 鎖釋放, 其他線程又可以競爭鎖
OK, 目前為止, 解決了鎖超時而業務邏輯仍在執行的鎖沖突問題, 還很簡陋, 而最嚴謹的方式還是使用官方的 Redlock 算法實現, 其中 Java 包推薦使用 redisson, 思路差不多其實, 都是在快要超時時續期, 以保證業務邏輯未執行完畢不會有其他客戶端持有鎖
總結
以上是生活随笔為你收集整理的redis desktop manager_面试官:Redis分布式锁如何解决锁超时问题?的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 微pe Linux,微PE工具箱 v2.
- 下一篇: android逆向工程dex2jar使用