- 基于數據庫的
- 基于redis
- 基于zookeeper
基于數據庫
基于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) {
// 若在這里程序突然崩潰,則無法設置過期時間,將發生死鎖
//也就是無法保證和上一個操作的原子性
jedis.expire(lockKey, expireTime);
}
}
改進版 redis 2.6.12
/**
* 獲取分布式鎖
* @param jedis Redis客戶端
* @param lockKey 鎖
* @param requestId 請求標識
* @expireTime 過期時間
*/
public static boolean tryGetDistributedLock(Jedis jedis, String lockKey, String requestId, int expireTime) {
// 這個方法就可以保證過期時間和獲取鎖操作的原子性
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用當前線程的id目的:
- 如果線程在過期時間結束之前完成任務,要執行del操作,釋放鎖。
- 如果線程A設置過期時間30s,30s之后該線程還沒有執行完,其他線程B獲取到鎖
- 這時候A執行完了,開始執行del,這時候就會刪除掉B獲取的鎖,所以要用requestId做判斷。
刪除操作
if(threadId.equals(jedis.get(lockkey))){
del(lockkey)
}
同樣的,判斷和刪除操作無法保證原子性!
這時候解決辦法就是用lua腳本
public class RedisTool {
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腳本
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
//eval 第一個是lua腳本 第二個是參數個數 從第三個開始是變量 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 要執行的腳本
* @param numbers 指定鍵名參數的個數
* @param key 腳本中的redis的key 從第三個參數開始 KEY[1] KEY[2]等等
* @return arg 附加參數 ARGV[1] ARGV[2]等等
*/
EVAL script numbers key[key ...] arg[arg ...]
為什么用腳本就能保證原子性呢?
補充
上面的情況。線程A沒有執行完,線程B獲取鎖,怎么解決呢?
用守護線程。 線程A在最后一秒還沒有執行完的話,就繼續增加過期時間。