- 基于數(shù)據(jù)庫的
- 基于redis
- 基于zookeeper
基于數(shù)據(jù)庫
基于redis
先來看第一種
public static void demo(Jedis jedis, String lockKey, String requestId, int expireTime) {
// setnx 是set if not exist,r如果不存在拱层,則插入搪桂,返回1 否則返回0
//lockkey就是需要獲取鎖的名稱或者id value是該線程id
Long result = jedis.setnx(lockKey, requestId);
if (result == 1) {
// 若在這里程序突然崩潰瘫里,則無法設(shè)置過期時(shí)間甥厦,將發(fā)生死鎖
//也就是無法保證和上一個(gè)操作的原子性
jedis.expire(lockKey, expireTime);
}
}
改進(jìn)版 redis 2.6.12
/**
* 獲取分布式鎖
* @param jedis Redis客戶端
* @param lockKey 鎖
* @param requestId 請(qǐng)求標(biāo)識(shí)
* @expireTime 過期時(shí)間
*/
public static boolean tryGetDistributedLock(Jedis jedis, String lockKey, String requestId, int expireTime) {
// 這個(gè)方法就可以保證過期時(shí)間和獲取鎖操作的原子性
String result = jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime);
if (LOCK_SUCCESS.equals(result)) {
return true;
}
return false;
}
}
上面value用當(dāng)前線程的id目的:
- 如果線程在過期時(shí)間結(jié)束之前完成任務(wù)翰意,要執(zhí)行del操作棒假,釋放鎖蜡饵。
- 如果線程A設(shè)置過期時(shí)間30s,30s之后該線程還沒有執(zhí)行完辐烂,其他線程B獲取到鎖
- 這時(shí)候A執(zhí)行完了遏插,開始執(zhí)行del,這時(shí)候就會(huì)刪除掉B獲取的鎖,所以要用requestId做判斷纠修。
刪除操作
if(threadId.equals(jedis.get(lockkey))){
del(lockkey)
}
同樣的,判斷和刪除操作無法保證原子性厂僧!
這時(shí)候解決辦法就是用lua腳本
public class RedisTool {
private static final Long RELEASE_SUCCESS = 1L;
/**
* 釋放分布式鎖
* @param jedis Redis客戶端
* @param lockKey 鎖
* @param requestId 請(qǐng)求標(biāo)識(shí)
* @return 是否釋放成功
*/
public static boolean releaseDistributedLock(Jedis jedis, String lockKey, String requestId) {
//這里用到lua腳本
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
//eval 第一個(gè)是lua腳本 第二個(gè)是參數(shù)個(gè)數(shù) 從第三個(gè)開始是變量 KEY[1] KEY[2]等 后面是附加
Object result = jedis.eval(script, 2扣草,,Collections.singletonList(lockKey), Collections.singletonList(requestId));
if (RELEASE_SUCCESS.equals(result)) {
return true;
}
return false;
}
}
/**
* @param script 要執(zhí)行的腳本
* @param numbers 指定鍵名參數(shù)的個(gè)數(shù)
* @param key 腳本中的redis的key 從第三個(gè)參數(shù)開始 KEY[1] KEY[2]等等
* @return arg 附加參數(shù) ARGV[1] ARGV[2]等等
*/
EVAL script numbers key[key ...] arg[arg ...]
為什么用腳本就能保證原子性呢?
補(bǔ)充
上面的情況颜屠。線程A沒有執(zhí)行完辰妙,線程B獲取鎖,怎么解決呢甫窟?
用守護(hù)線程密浑。 線程A在最后一秒還沒有執(zhí)行完的話,就繼續(xù)增加過期時(shí)間粗井。