分布式鎖(Redis)

  1. 基于數據庫的
  2. 基于redis
  3. 基于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在最后一秒還沒有執行完的話,就繼續增加過期時間。

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容