redisLock redis分布式鎖
redis setnx 命令
redis setnx 命令特性
當指定key不存在時才設置。也就是說,如果返回1說明你的命令被執行成功了,redis服務器中的key是你之前設置的值。如果返回0,說明你設置的key在redis服務器裏已經存在。
status = jedis.setnx(lockKey, redisIdentityKey);/**設置 lock key.*/ if (status > 0) { expire = jedis.expire(lockKey, lockKeyExpireSecond);/**set redis key expire time.*/ }
如果設置成功了,才進行過期時間設置,防止你的retry lock重復設置這個過期時間,導致永遠不過期。
java object condition queue 條件隊列
這裏有一個小竅門,可以盡可能的最大化cpu利用率又可以解決公平性問題。
當你頻繁retry的時候,要麽while(true)死循環,然後加個Thread.sleep,或者CAS。前者存在一定線程上下文切換開銷(Thread.sleep是不會釋放出當前內置鎖),而CAS在不清楚遠程鎖被占用多久的情況會浪費很多CPU計算周期,有可能一個任務計算個十幾分鐘,CPU不可能空轉這麽久。
這裏我嘗試使用condition queue條件隊列特性來實現(當然肯定還有其他更優的方法)。
if (isWait && retryCounts < RetryCount) { retryCounts++; synchronized (this) {//借助object condition queue 來提高CPU利用率 logger.info(String. format("t:%s,當前節點:%s,嘗試等待獲取鎖:%s", Thread.currentThread().getId(), getRedisIdentityKey(), lockKey)); this.wait(WaitLockTimeSecond); //未能獲取到lock,進行指定時間的wait再重試. } } else if (retryCounts == RetryCount) { logger.info(String. format("t:%s,當前節點:%s,指定時間內獲取鎖失敗:%s", Thread.currentThread().getId(), getRedisIdentityKey(), lockKey)); return false; } else { return false;//不需要等待,直接退出。 }
使用條件隊列的好處就是,它雖然釋放出了CPU但是也不會持有當前synchronized,這樣就可以讓其他並發進來的線程也可以獲取到當前內置鎖,然後形成隊列。當wait時間到了被調度喚醒之後才會重新來申請synchronized鎖。
簡單講就是不會再鎖上等待而是在隊列裏等待。java object每一個對象都持有一個條件隊列,與當前內置鎖配合使用。
retrycount 帶有重試次數限制
等待遠程redis lock肯定是需要一定重試機制,但是這種重試是需要一定的限制。
/** * 重試獲取鎖的次數,可以根據當前任務的執行時間來設置。 * 需要時間=RetryCount*(WaitLockTimeSecond/1000) */ private static final int RetryCount = 10;
這種等待是需要用戶指定的, if (isWait && retryCounts < RetryCount) ,當isWait為true才會進行重試。
object wait time 帶有超時時間的wait
object.wait(timeout),條件隊列中的方法wait是需要一個waittime。
/** * 等待獲取鎖的時間,可以根據當前任務的執行時間來設置。 * 設置的太短,浪費CPU,設置的太長鎖就不太公平。 */ private static final long WaitLockTimeSecond = 2000;
默認2000毫秒。
this.wait(WaitLockTimeSecond); //未能獲取到lock,進行指定時間的wait再重試.
註意:this.wait雖然會blocking住,但是這裏的內置鎖是會立即釋放出來的。所以,有時候我們可以借助這種特性來優化特殊場景。
delete lock 刪除遠程鎖
釋放redis lock比較簡單,直接del key就好了
long status = jedis.del(lockKey); if (status > 0) { logger.info(String. format("t:%s,當前節點:%s,釋放鎖:%s 成功。", Thread.currentThread().getId(), getRedisIdentityKey(), lockKey)); return true; }
一旦delete 之後,首先wait喚醒的線程將會獲得鎖。
acquire lock 申請lock
/** * 帶超時時間的redis lock. * * @param lockKeyExpireSecond 鎖key在redis中的過去時間 * @param lockKey lock key * @param isWait 當獲取不到鎖時是否需要等待 * @throws Exception lockKey is empty throw exception. */ public Boolean acquireLockWithTimeout(int lockKeyExpireSecond, String lockKey, Boolean isWait) throws Exception { if (StringUtils.isEmpty(lockKey)) throw new Exception("lockKey is empty."); int retryCounts = 0; while (true) { Long status, expire = 0L; status = jedis.setnx(lockKey, redisIdentityKey);/**設置 lock key.*/ if (status > 0) { expire = jedis.expire(lockKey, lockKeyExpireSecond);/**set redis key expire time.*/ } if (status > 0 && expire > 0) { logger.info(String. format("t:%s,當前節點:%s,獲取到鎖:%s", Thread.currentThread().getId(), getRedisIdentityKey(), lockKey)); return true;/**獲取到lock*/ } try { if (isWait && retryCounts < RetryCount) { retryCounts++; synchronized (this) {//借助object condition queue 來提高CPU利用率 logger.info(String. format("t:%s,當前節點:%s,嘗試等待獲取鎖:%s", Thread.currentThread().getId(), getRedisIdentityKey(), lockKey)); this.wait(WaitLockTimeSecond); //未能獲取到lock,進行指定時間的wait再重試. } } else if (retryCounts == RetryCount) { logger.info(String. format("t:%s,當前節點:%s,指定時間內獲取鎖失敗:%s", Thread.currentThread().getId(), getRedisIdentityKey(), lockKey)); return false; } else { return false;//不需要等待,直接退出。 } } catch (InterruptedException e) { e.printStackTrace(); } } }
## release lock 釋放lock/** * 釋放redis lock。 * * @param lockKey lock key * @throws Exception lockKey is empty throw exception. */ public Boolean releaseLockWithTimeout(String lockKey) throws Exception { if (StringUtils.isEmpty(lockKey)) throw new Exception("lockKey is empty."); long status = jedis.del(lockKey); if (status > 0) { logger.info(String.format("當前節點:%s,釋放鎖:%s 成功。", getRedisIdentityKey(), lockKey)); return true; } logger.info(String.format("當前節點:%s,釋放鎖:%s 失敗。", getRedisIdentityKey(), lockKey)); return false; }
demo 演示
2017-06-18 13:57:43.867 INFO 1444 --- [nio-8080-exec-1] c.plen.opensource.implement.RedisLocker : t:23,當前節點:5f81f482-295a-4394-b8cb-d7282e51dd6e,獲取到鎖:product:10100101:shopping
2017-06-18 13:57:47.062 INFO 1444 --- [nio-8080-exec-3] c.plen.opensource.implement.RedisLocker : t:25,當前節點:5f81f482-295a-4394-b8cb-d7282e51dd6e,嘗試等待獲取鎖:product:10100101:shopping
2017-06-18 13:57:49.063 INFO 1444 --- [nio-8080-exec-3] c.plen.opensource.implement.RedisLocker : t:25,當前節點:5f81f482-295a-4394-b8cb-d7282e51dd6e,嘗試等待獲取鎖:product:10100101:shopping
2017-06-18 13:57:51.064 INFO 1444 --- [nio-8080-exec-3] c.plen.opensource.implement.RedisLocker : t:25,當前節點:5f81f482-295a-4394-b8cb-d7282e51dd6e,嘗試等待獲取鎖:product:10100101:shopping
2017-06-18 13:57:53.066 INFO 1444 --- [nio-8080-exec-3] c.plen.opensource.implement.RedisLocker : t:25,當前節點:5f81f482-295a-4394-b8cb-d7282e51dd6e,嘗試等待獲取鎖:product:10100101:shopping
2017-06-18 13:57:55.068 INFO 1444 --- [nio-8080-exec-3] c.plen.opensource.implement.RedisLocker : t:25,當前節點:5f81f482-295a-4394-b8cb-d7282e51dd6e,嘗試等待獲取鎖:product:10100101:shopping
2017-06-18 13:57:57.069 INFO 1444 --- [nio-8080-exec-3] c.plen.opensource.implement.RedisLocker : t:25,當前節點:5f81f482-295a-4394-b8cb-d7282e51dd6e,嘗試等待獲取鎖:product:10100101:shopping
2017-06-18 13:57:59.070 INFO 1444 --- [nio-8080-exec-3] c.plen.opensource.implement.RedisLocker : t:25,當前節點:5f81f482-295a-4394-b8cb-d7282e51dd6e,嘗試等待獲取鎖:product:10100101:shopping
2017-06-18 13:58:01.071 INFO 1444 --- [nio-8080-exec-3] c.plen.opensource.implement.RedisLocker : t:25,當前節點:5f81f482-295a-4394-b8cb-d7282e51dd6e,嘗試等待獲取鎖:product:10100101:shopping
2017-06-18 13:58:03.072 INFO 1444 --- [nio-8080-exec-3] c.plen.opensource.implement.RedisLocker : t:25,當前節點:5f81f482-295a-4394-b8cb-d7282e51dd6e,嘗試等待獲取鎖:product:10100101:shopping
2017-06-18 13:58:05.073 INFO 1444 --- [nio-8080-exec-3] c.plen.opensource.implement.RedisLocker : t:25,當前節點:5f81f482-295a-4394-b8cb-d7282e51dd6e,嘗試等待獲取鎖:product:10100101:shopping
2017-06-18 13:58:07.074 INFO 1444 --- [nio-8080-exec-3] c.plen.opensource.implement.RedisLocker : t:25,當前節點:5f81f482-295a-4394-b8cb-d7282e51dd6e,指定時間內獲取鎖失敗:product:10100101:shopping
2017-06-18 13:58:23.768 INFO 1444 --- [nio-8080-exec-6] c.plen.opensource.implement.RedisLocker : t:28,當前節點:5f81f482-295a-4394-b8cb-d7282e51dd6e,嘗試等待獲取鎖:product:10100101:shopping
2017-06-18 13:58:25.769 INFO 1444 --- [nio-8080-exec-6] c.plen.opensource.implement.RedisLocker : t:28,當前節點:5f81f482-295a-4394-b8cb-d7282e51dd6e,嘗試等待獲取鎖:product:10100101:shopping
2017-06-18 13:58:27.770 INFO 1444 --- [nio-8080-exec-6] c.plen.opensource.implement.RedisLocker : t:28,當前節點:5f81f482-295a-4394-b8cb-d7282e51dd6e,嘗試等待獲取鎖:product:10100101:shopping
2017-06-18 13:58:29.772 INFO 1444 --- [nio-8080-exec-6] c.plen.opensource.implement.RedisLocker : t:28,當前節點:5f81f482-295a-4394-b8cb-d7282e51dd6e,嘗試等待獲取鎖:product:10100101:shopping
2017-06-18 13:58:31.773 INFO 1444 --- [nio-8080-exec-6] c.plen.opensource.implement.RedisLocker : t:28,當前節點:5f81f482-295a-4394-b8cb-d7282e51dd6e,嘗試等待獲取鎖:product:10100101:shopping
2017-06-18 13:58:33.774 INFO 1444 --- [nio-8080-exec-6] c.plen.opensource.implement.RedisLocker : t:28,當前節點:5f81f482-295a-4394-b8cb-d7282e51dd6e,嘗試等待獲取鎖:product:10100101:shopping
2017-06-18 13:58:35.774 INFO 1444 --- [nio-8080-exec-6] c.plen.opensource.implement.RedisLocker : t:28,當前節點:5f81f482-295a-4394-b8cb-d7282e51dd6e,獲取到鎖:product:10100101:shopping
thread 23 優先獲取到對商品ID 10100101 進行修改,所以先鎖住當前商品。
t:23,當前節點:843d3ec0-9c22-4d8a-bcaa-745dba35b8a4,獲取到鎖:product:10100101:shopping
緊接著,thread 25也來對當前商品 10100101進行修改,所以在嘗試獲取鎖。
2017-06-18 13:50:11.021 INFO 4616 --- [nio-8080-exec-3] c.plen.opensource.implement.RedisLocker : t:25,當前節點:946b7250-29f3-459b-8320-62d31e6f1fc4,嘗試等待獲取鎖:product:10100101:shopping
2017-06-18 13:50:13.023 INFO 4616 --- [nio-8080-exec-3] c.plen.opensource.implement.RedisLocker : t:25,當前節點:946b7250-29f3-459b-8320-62d31e6f1fc4,嘗試等待獲取鎖:product:10100101:shopping
2017-06-18 13:50:15.026 INFO 4616 --- [nio-8080-exec-3] c.plen.opensource.implement.RedisLocker : t:25,當前節點:946b7250-29f3-459b-8320-62d31e6f1fc4,嘗試等待獲取鎖:product:10100101:shopping
2017-06-18 13:50:17.028 INFO 4616 --- [nio-8080-exec-3] c.plen.opensource.implement.RedisLocker : t:25,當前節點:946b7250-29f3-459b-8320-62d31e6f1fc4,嘗試等待獲取鎖:product:10100101:shopping
2017-06-18 13:50:19.030 INFO 4616 --- [nio-8080-exec-3] c.plen.opensource.implement.RedisLocker : t:25,當前節點:946b7250-29f3-459b-8320-62d31e6f1fc4,嘗試等待獲取鎖:product:10100101:shopping
2017-06-18 13:50:21.031 INFO 4616 --- [nio-8080-exec-3] c.plen.opensource.implement.RedisLocker : t:25,當前節點:946b7250-29f3-459b-8320-62d31e6f1fc4,嘗試等待獲取鎖:product:10100101:shopping
2017-06-18 13:50:23.035 INFO 4616 --- [nio-8080-exec-3] c.plen.opensource.implement.RedisLocker : t:25,當前節點:946b7250-29f3-459b-8320-62d31e6f1fc4,嘗試等待獲取鎖:product:10100101:shopping
2017-06-18 13:50:25.037 INFO 4616 --- [nio-8080-exec-3] c.plen.opensource.implement.RedisLocker : t:25,當前節點:946b7250-29f3-459b-8320-62d31e6f1fc4,嘗試等待獲取鎖:product:10100101:shopping
2017-06-18 13:50:27.041 INFO 4616 --- [nio-8080-exec-3] c.plen.opensource.implement.RedisLocker : t:25,當前節點:946b7250-29f3-459b-8320-62d31e6f1fc4,嘗試等待獲取鎖:product:10100101:shopping
2017-06-18 13:50:29.042 INFO 4616 --- [nio-8080-exec-3] c.plen.opensource.implement.RedisLocker : t:25,當前節點:946b7250-29f3-459b-8320-62d31e6f1fc4,嘗試等待獲取鎖:product:10100101:shopping
2017-06-18 13:50:35.289 INFO 4616 --- [nio-8080-exec-3] c.plen.opensource.implement.RedisLocker : t:25,當前節點:946b7250-29f3-459b-8320-62d31e6f1fc4,指定時間內獲取鎖失敗:product:10100101:shopping
在進行了retry10次(2000毫秒,2秒)之後,獲取失敗,直接返回,等待下次任務調度開始。
2017-06-18 13:58:07.074 INFO 1444 --- [nio-8080-exec-3] c.plen.opensource.implement.RedisLocker : t:25,當前節點:5f81f482-295a-4394-b8cb-d7282e51dd6e,指定時間內獲取鎖失敗:product:10100101:shopping
2017-06-18 13:58:23.768 INFO 1444 --- [nio-8080-exec-6] c.plen.opensource.implement.RedisLocker : t:28,當前節點:5f81f482-295a-4394-b8cb-d7282e51dd6e,嘗試等待獲取鎖:product:10100101:shopping
2017-06-18 13:58:25.769 INFO 1444 --- [nio-8080-exec-6] c.plen.opensource.implement.RedisLocker : t:28,當前節點:5f81f482-295a-4394-b8cb-d7282e51dd6e,嘗試等待獲取鎖:product:10100101:shopping
2017-06-18 13:58:27.770 INFO 1444 --- [nio-8080-exec-6] c.plen.opensource.implement.RedisLocker : t:28,當前節點:5f81f482-295a-4394-b8cb-d7282e51dd6e,嘗試等待獲取鎖:product:10100101:shopping
2017-06-18 13:58:29.772 INFO 1444 --- [nio-8080-exec-6] c.plen.opensource.implement.RedisLocker : t:28,當前節點:5f81f482-295a-4394-b8cb-d7282e51dd6e,嘗試等待獲取鎖:product:10100101:shopping
2017-06-18 13:58:31.773 INFO 1444 --- [nio-8080-exec-6] c.plen.opensource.implement.RedisLocker : t:28,當前節點:5f81f482-295a-4394-b8cb-d7282e51dd6e,嘗試等待獲取鎖:product:10100101:shopping
2017-06-18 13:58:33.774 INFO 1444 --- [nio-8080-exec-6] c.plen.opensource.implement.RedisLocker : t:28,當前節點:5f81f482-295a-4394-b8cb-d7282e51dd6e,嘗試等待獲取鎖:product:10100101:shopping
2017-06-18 13:58:35.774 INFO 1444 --- [nio-8080-exec-6] c.plen.opensource.implement.RedisLocker : t:28,當前節點:5f81f482-295a-4394-b8cb-d7282e51dd6e,獲取到鎖:product:10100101:shopping
thread 28 發起對商品 10100101 進行修改,retry6次之後獲取到lock。
redisLock redis分布式鎖