1.前言
對每個controller來說都是全新且單獨的,原因是多線程,如果多個請求操作共有的數據,這樣的並發操作會導致臟數據
怎么解決?
mysql可以使用積極鎖解決,
這里講解的是redis的解決辦法,雖然有幾種解決辦法,但我這里只記錄最好的:setnx指令算法加鎖,思路與mysql的消極鎖相似
2.redis鎖需要滿足幾個要求:
(1)只能讓一個客戶端加鎖,當鎖存在時其他客戶端不可以加鎖
(2)只能讓加鎖的客戶端解鎖,不允許其他客戶端解鎖
(3)當鎖存在時,加鎖失敗的客戶端需要等待解鎖后自己加鎖,只有自己加鎖成功后才可以操作共有數據,即阻塞操作
(4)不能產生死鎖,如果加鎖的客戶端還沒有解鎖前就因為某些原因就崩潰了,鎖自動解鎖,不影響其他客戶端操作
3.原理
(1)加鎖 則是 使用setnx指令,新建一個鍵值對,如果成功會返回 OK ,即視為加鎖成功,如果已經存在,則返回空 ,視為加鎖失敗
(2)為了確保不產生死鎖,應該對這個數據設置存活時間,如果崩潰后,到時間會自動刪除
(3)解鎖 則是 使用Lua腳本語句對鍵值對判斷這個鎖是不是自己加的,如果時別人的或者不存在,則不操作,如果是自己的則做刪除鍵值對操作
(4)無論時加鎖還是解鎖操作,為了確保邏輯的原子性,都應該是個多參數操作指令,有些低版本的redis不支持,才會將指令拆分成多條順序執行
但是容易導致邏輯問題,形成臟數據,
4.封裝工具
我做的一個工具類,輸入參數即可完成加鎖 解鎖操作 ,這是單機的,如果是分布式則改將jedis對象改成分布式對象ShardedJedisPool即可,思路一樣

package cn.cen2guo.clinic.redis; import redis.clients.jedis.Jedis; import java.util.Collections; public class JedisLock { //加鎖檢查 //加鎖標識,返回結果是這個則說明加鎖成功 private static final String LOCK_SUCCESS = "OK"; //定義set方法的使用方式,如果不存在則以 lockKey 為 key ,requestId 為value 新建string類型鍵值 , private static final String SET_IF_NOT_EXIST = "NX"; //表示開啟存活時間,PX是毫秒數 ,如果想以秒為單位則設為EX private static final String SET_WITH_EXPIRE_TIME = "PX"; /** * 嘗試獲取鎖[加鎖] * * @param jedis Redis客戶端 * @param lockKey 鎖 * @param requestId 請求標識 * @param expireTime 超期時間 * @return 是否獲取成功 */ public static boolean tryGetDistributedLock(Jedis jedis, String lockKey, String requestId, int expireTime) { //語句意思是,判斷lockKey 這個key是否存在,不存在則以 lockKey 為 key ,requestId 為value 新建string類型鍵值 , //並開啟存活時間,單位毫秒, //新建成功返回結果 OK ,說明加鎖成功 // 失敗則為空,說明加鎖失敗 String result = jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime); if (LOCK_SUCCESS.equals(result)) { return true; } return false; } // // // // // // // // // //解鎖標識,返回結果是這個則說明解鎖成功 private static final Long RELEASE_SUCCESS = 1L; /** * 釋放鎖 * * @param jedis Redis客戶端 * * @param lockKey 鎖 * * @param requestId 請求標識 * * @return 是否釋放成功 */ public static boolean releaseDistributedLock(Jedis jedis, String lockKey, String requestId) { // 使用Lua腳本,執行判斷語句, //刪除成功則返回結果1L,失敗則為0 String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end"; Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(requestId)); System.out.println("解鎖結果是=="+result); if (RELEASE_SUCCESS.equals(result)) { return true; } return false; } }
5.使用
加鎖

/** * 給等待池加鎖 */ //鎖的key名,自定義 String lockKey = "lock" ; //唯一識別是哪個用戶端的鎖id // 標准的UUID格式為:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx (8-4-4-4-12)。 //時間戳+隨機數 String requestId = System.currentTimeMillis() + UUID.randomUUID().toString(); //存活時間,10秒,防止崩潰造成死鎖 int expireTime = 10000; //獲取jedis對象 Jedis jedis = jedisMyGetJedis.myGetJedis(); //加鎖操作 boolean f = JedisLock.tryGetDistributedLock(jedis, lockKey, requestId, expireTime); if (!f) { // 失敗,鎖已經存在 //休眠0.5秒后再次加鎖操作 System.out.println("加鎖失敗,鎖已經存在" + new Date()); int k = 0; while (k == 0) { System.out.println("睡眠一次" + new Date()); //當前線程休眠0.5秒 Thread.sleep(500); boolean f2 = JedisLock.tryGetDistributedLock(jedis, lockKey, requestId, expireTime); if (f2) { //加鎖成功 // 退出循環 k = 1; } } } //加鎖成功 System.out.println("加鎖成功" + new Date()); //下面的操作只會有一個人操作,因此不需要擔心有並發操作
解鎖

//解鎖 boolean r = JedisLock.releaseDistributedLock(jedis, lockKey, requestId); if (r) { System.out.println("解鎖成功"); } else { System.out.println("解鎖失敗"); } //關閉jedis對象 jedis.close();