redis-lock
- redis setnx cmmand
- java object condition queue 條件隊(duì)列
- retrycount 帶有重試次數(shù)限制
- object wait time 帶有超時(shí)時(shí)間的wait
- delete lock 刪除遠(yuǎn)程鎖
- acquire lock 申請lock
- release lock 釋放lock
- demo 演示
- 鎖的粒度問題,鎖分解、鎖分段
- github https://github.com/Plen-wang/redis-lock
redis setnx 命令
redis setnx 命令特性
當(dāng)指定key不存在時(shí)才設(shè)置。也就是說,如果返回1說明你的命令被執(zhí)行成功了,redis服務(wù)器中的key是你之前設(shè)置的值。如果返回0,說明你設(shè)置的key在redis服務(wù)器里已經(jīng)存在。
status = jedis.setnx(lockKey, redisIdentityKey);/**設(shè)置 lock key.*/
if (status > 0) {
expire = jedis.expire(lockKey, lockKeyExpireSecond);/**set redis key expire time.*/
}
如果設(shè)置成功了,才進(jìn)行過期時(shí)間設(shè)置,防止你的retry lock重復(fù)設(shè)置這個(gè)過期時(shí)間,導(dǎo)致永遠(yuǎn)不過期。
java object condition queue 條件隊(duì)列
這里有一個(gè)小竅門,可以盡可能的最大化cpu利用率又可以解決公平性問題。
當(dāng)你頻繁retry的時(shí)候,要么while(true)死循環(huán),然后加個(gè)Thread.sleep,或者CAS。前者存在一定線程上下文切換開銷(Thread.sleep是不會釋放出當(dāng)前內(nèi)置鎖),而CAS在不清楚遠(yuǎn)程鎖被占用多久的情況會浪費(fèi)很多CPU計(jì)算周期,有可能一個(gè)任務(wù)計(jì)算個(gè)十幾分鐘,CPU不可能空轉(zhuǎn)這么久。
這里我嘗試使用condition queue條件隊(duì)列特性來實(shí)現(xiàn)(當(dāng)然肯定還有其他更優(yōu)的方法)。
if (isWait && retryCounts < RetryCount) {
retryCounts++;
synchronized (this) {//借助object condition queue 來提高CPU利用率
logger.info(String.
format("t:%s,當(dāng)前節(jié)點(diǎn):%s,嘗試等待獲取鎖:%s", Thread.currentThread().getId(), getRedisIdentityKey(), lockKey));
this.wait(WaitLockTimeSecond); //未能獲取到lock,進(jìn)行指定時(shí)間的wait再重試.
}
} else if (retryCounts == RetryCount) {
logger.info(String.
format("t:%s,當(dāng)前節(jié)點(diǎn):%s,指定時(shí)間內(nèi)獲取鎖失敗:%s", Thread.currentThread().getId(), getRedisIdentityKey(), lockKey));
return false;
} else {
return false;//不需要等待,直接退出。
}
使用條件隊(duì)列的好處就是,它雖然釋放出了CPU但是也不會持有當(dāng)前synchronized,這樣就可以讓其他并發(fā)進(jìn)來的線程也可以獲取到當(dāng)前內(nèi)置鎖,然后形成隊(duì)列。當(dāng)wait時(shí)間到了被調(diào)度喚醒之后才會重新來申請synchronized鎖。
簡單講就是不會再鎖上等待而是在隊(duì)列里等待。java object每一個(gè)對象都持有一個(gè)條件隊(duì)列,與當(dāng)前內(nèi)置鎖配合使用。
retrycount 帶有重試次數(shù)限制
等待遠(yuǎn)程redis lock肯定是需要一定重試機(jī)制,但是這種重試是需要一定的限制。
/**
* 重試獲取鎖的次數(shù),可以根據(jù)當(dāng)前任務(wù)的執(zhí)行時(shí)間來設(shè)置。
* 需要時(shí)間=RetryCount*(WaitLockTimeSecond/1000)
*/
private static final int RetryCount = 10;
這種等待是需要用戶指定的, if (isWait && retryCounts < RetryCount) ,當(dāng)isWait為true才會進(jìn)行重試。
object wait time 帶有超時(shí)時(shí)間的wait
object.wait(timeout),條件隊(duì)列中的方法wait是需要一個(gè)waittime。
/**
* 等待獲取鎖的時(shí)間,可以根據(jù)當(dāng)前任務(wù)的執(zhí)行時(shí)間來設(shè)置。
* 設(shè)置的太短,浪費(fèi)CPU,設(shè)置的太長鎖就不太公平。
*/
private static final long WaitLockTimeSecond = 2000;
默認(rèn)2000毫秒。
this.wait(WaitLockTimeSecond); //未能獲取到lock,進(jìn)行指定時(shí)間的wait再重試.
注意:this.wait雖然會blocking住,但是這里的內(nèi)置鎖是會立即釋放出來的。所以,有時(shí)候我們可以借助這種特性來優(yōu)化特殊場景。
delete lock 刪除遠(yuǎn)程鎖
釋放redis lock比較簡單,直接del key就好了
long status = jedis.del(lockKey);
if (status > 0) {
logger.info(String.
format("t:%s,當(dāng)前節(jié)點(diǎn):%s,釋放鎖:%s 成功。", Thread.currentThread().getId(), getRedisIdentityKey(), lockKey));
return true;
}
一旦delete 之后,首先wait喚醒的線程將會獲得鎖。
acquire lock 申請lock
/**
* 帶超時(shí)時(shí)間的redis lock.
*
* @param lockKeyExpireSecond 鎖key在redis中的過去時(shí)間
* @param lockKey lock key
* @param isWait 當(dāng)獲取不到鎖時(shí)是否需要等待
* @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);/**設(shè)置 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,當(dāng)前節(jié)點(diǎn):%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,當(dāng)前節(jié)點(diǎn):%s,嘗試等待獲取鎖:%s", Thread.currentThread().getId(), getRedisIdentityKey(), lockKey));
this.wait(WaitLockTimeSecond); //未能獲取到lock,進(jìn)行指定時(shí)間的wait再重試.
}
} else if (retryCounts == RetryCount) {
logger.info(String.
format("t:%s,當(dāng)前節(jié)點(diǎn):%s,指定時(shí)間內(nèi)獲取鎖失?。?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("當(dāng)前節(jié)點(diǎn):%s,釋放鎖:%s 成功。", getRedisIdentityKey(), lockKey));
return true;
}
logger.info(String.format("當(dāng)前節(jié)點(diǎn):%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,當(dāng)前節(jié)點(diǎn):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,當(dāng)前節(jié)點(diǎn):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,當(dāng)前節(jié)點(diǎn):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,當(dāng)前節(jié)點(diǎn):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,當(dāng)前節(jié)點(diǎn):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,當(dāng)前節(jié)點(diǎn):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,當(dāng)前節(jié)點(diǎn):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,當(dāng)前節(jié)點(diǎn):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,當(dāng)前節(jié)點(diǎn):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,當(dāng)前節(jié)點(diǎn):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,當(dāng)前節(jié)點(diǎn):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,當(dāng)前節(jié)點(diǎn):5f81f482-295a-4394-b8cb-d7282e51dd6e,指定時(shí)間內(nèi)獲取鎖失?。簆roduct:10100101:shopping
2017-06-18 13:58:23.768 INFO 1444 --- [nio-8080-exec-6] c.plen.opensource.implement.RedisLocker : t:28,當(dāng)前節(jié)點(diǎn):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,當(dāng)前節(jié)點(diǎn):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,當(dāng)前節(jié)點(diǎn):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,當(dāng)前節(jié)點(diǎn):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,當(dāng)前節(jié)點(diǎn):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,當(dāng)前節(jié)點(diǎn):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,當(dāng)前節(jié)點(diǎn):5f81f482-295a-4394-b8cb-d7282e51dd6e,獲取到鎖:product:10100101:shopping
thread 23 優(yōu)先獲取到對商品ID 10100101 進(jìn)行修改,所以先鎖住當(dāng)前商品。
t:23,當(dāng)前節(jié)點(diǎn):843d3ec0-9c22-4d8a-bcaa-745dba35b8a4,獲取到鎖:product:10100101:shopping
緊接著,thread 25也來對當(dāng)前商品 10100101進(jìn)行修改,所以在嘗試獲取鎖。
2017-06-18 13:50:11.021 INFO 4616 --- [nio-8080-exec-3] c.plen.opensource.implement.RedisLocker : t:25,當(dāng)前節(jié)點(diǎn):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,當(dāng)前節(jié)點(diǎn):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,當(dāng)前節(jié)點(diǎn):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,當(dāng)前節(jié)點(diǎn):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,當(dāng)前節(jié)點(diǎn):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,當(dāng)前節(jié)點(diǎn):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,當(dāng)前節(jié)點(diǎn):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,當(dāng)前節(jié)點(diǎn):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,當(dāng)前節(jié)點(diǎn):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,當(dāng)前節(jié)點(diǎn):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,當(dāng)前節(jié)點(diǎn):946b7250-29f3-459b-8320-62d31e6f1fc4,指定時(shí)間內(nèi)獲取鎖失?。簆roduct:10100101:shopping
在進(jìn)行了retry10次(2000毫秒,2秒)之后,獲取失敗,直接返回,等待下次任務(wù)調(diào)度開始。
2017-06-18 13:58:07.074 INFO 1444 --- [nio-8080-exec-3] c.plen.opensource.implement.RedisLocker : t:25,當(dāng)前節(jié)點(diǎn):5f81f482-295a-4394-b8cb-d7282e51dd6e,指定時(shí)間內(nèi)獲取鎖失敗:product:10100101:shopping
2017-06-18 13:58:23.768 INFO 1444 --- [nio-8080-exec-6] c.plen.opensource.implement.RedisLocker : t:28,當(dāng)前節(jié)點(diǎn):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,當(dāng)前節(jié)點(diǎn):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,當(dāng)前節(jié)點(diǎn):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,當(dāng)前節(jié)點(diǎn):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,當(dāng)前節(jié)點(diǎn):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,當(dāng)前節(jié)點(diǎn):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,當(dāng)前節(jié)點(diǎn):5f81f482-295a-4394-b8cb-d7282e51dd6e,獲取到鎖:product:10100101:shopping
thread 28 發(fā)起對商品 10100101 進(jìn)行修改,retry6次之后獲取到lock。
鎖的粒度問題,鎖分解、鎖分段
這里的例子比較簡單。如果在并發(fā)比較大的情況下是需要結(jié)合鎖分解、鎖分段來進(jìn)行優(yōu)化的。
修改商品,沒有必要鎖住整個(gè)商品庫,只需要鎖住你需要修改的指定ID的商品。也可以借鑒鎖分段思路,將數(shù)據(jù)按照一定維度進(jìn)行劃分,然后加上不同維度的鎖,可以提升CPU性能??梢愿鶕?jù)商品catagory來設(shè)計(jì)段鎖或者batch來設(shè)計(jì)段鎖。
github
源碼已提交gihub,代碼如有不對請多指教。
github地址:https://github.com/Plen-wang/redis-lock
|