使用RedisTemplate+Lua腳本實現Redis分佈式鎖

分佈式鎖通常有三種實現方式:1. 數據庫樂觀鎖;2. 基於Redis的分佈式鎖;3. 基於ZooKeeper的分佈式鎖。本篇博客將介紹第二種方式,基於Redis實現分佈式鎖。html

首先,爲了確保分佈式鎖可用,咱們至少要確保鎖的實現同時知足如下四個條件:java

  1. 互斥性。在任意時刻,只有一個客戶端能持有鎖。
  2. 不會發生死鎖。即便有一個客戶端在持有鎖的期間崩潰而沒有主動解鎖,也能保證後續其餘客戶端能加鎖。
  3. 具備容錯性。只要大部分的Redis節點正常運行,客戶端就能夠加鎖和解鎖。
  4. 解鈴還須繫鈴人。加鎖和解鎖必須是同一個客戶端,客戶端本身不能把別人加的鎖給解了。

上面摘自博客Redis分佈式鎖的正確實現方式,下面我將經過Spring Boot的RedisTemplate實現分佈式鎖,不足之處請指出!redis

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Component;

import java.util.Arrays;
import java.util.concurrent.TimeUnit;

@Component
public class RedisLock {
    @Autowired
    private StringRedisTemplate template;
    @Autowired
    private DefaultRedisScript<Long> redisScript;

    private static final Long RELEASE_SUCCESS = 1L;

    private long timeout = 3000;

    public boolean lock(String key, String value) {
        long start = System.currentTimeMillis();
        while (true) {
            //檢測是否超時
            if (System.currentTimeMillis() - start > timeout) {
                return false;
            }
            //執行set命令
            Boolean absent = template.opsForValue().setIfAbsent(key, value, timeout, TimeUnit.MILLISECONDS);//1
            //其實不必判NULL,這裏是爲了程序的嚴謹而加的邏輯
            if (absent == null) {
                return false;
            }
            //是否成功獲取鎖
            if (absent) {
                return true;
            }
        }
    }

    public boolean unlock(String key, String value) {
        //使用Lua腳本:先判斷是不是本身設置的鎖,再執行刪除
        Long result = template.execute(redisScript, Arrays.asList(key,value));
        //返回最終結果
        return RELEASE_SUCCESS.equals(result);
    }

    public void setTimeout(long timeout) {
        this.timeout = timeout;
    }

    @Bean
    public DefaultRedisScript<Long> defaultRedisScript() {
        DefaultRedisScript<Long> defaultRedisScript = new DefaultRedisScript<>();
        defaultRedisScript.setResultType(Long.class);
        defaultRedisScript.setScriptText("if redis.call('get', KEYS[1]) == KEYS[2] then return redis.call('del', KEYS[1]) else return 0 end");
//        defaultRedisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("delete.lua")));
        return defaultRedisScript;
    }

}
相關文章
相關標籤/搜索