Redis實現分佈式鎖

一、Redis分佈式鎖實現思路

Redis實現分佈式鎖基於SetNx命令,因爲在redis中key是保證是唯一的。所以當多個線程同時創建setNx時,只要誰能夠創建成功誰就能獲取到鎖。

Set命令:每次set時,可以修改原來舊值;

SetNx命令:每次SetNx檢查key是否已經存在,如果已經存在的話就不會執行任何操作,返回0;反之,新增該key。

獲取鎖的時候:當多個線程同時創建SetNx key,只要誰能夠創建成功誰就能夠獲取到鎖。

釋放鎖:可以對該key設置一個有效期可以避免死鎖的現象。

二、Zookeeper實現分佈式鎖思路

Zookeeper實現分佈式鎖核心採用臨時節點+事件通知,因爲Zookeeper節點路徑是保證全局唯一的,當多個線程同時創建該臨時節點,只要誰能夠創建成功誰就能獲取到鎖。

獲取鎖:當多個線程同時創建該臨時節點,只要誰能夠創建成功誰就能夠獲取到鎖。

釋放鎖:關閉當前Session連接,自動的刪除當前的zk節點路徑,其他線程重新進入到獲取鎖階段。

三、分佈式鎖應用場景

  1. 分佈式任務調度平臺保證任務的冪等性。

  2. 分佈式全局id的生成

四、Redis分佈式鎖核心代碼

public class RedisUtil {
    //protected static Logger logger = Logger.getLogger(RedisUtil.class);
    
    private static String IP = "192.168.75.128";

    //Redis的端口號
    private static int PORT = 6379;

    //可用連接實例的最大數目,默認值爲8;
    //如果賦值爲-1,則表示不限制;如果pool已經分配了maxActive個jedis實例,則此時pool的狀態爲exhausted(耗盡)。
    private static int MAX_ACTIVE = 100;

    //控制一個pool最多有多少個狀態爲idle(空閒的)的jedis實例,默認值也是8。
    private static int MAX_IDLE = 20;

    //等待可用連接的最大時間,單位毫秒,默認值爲-1,表示永不超時。如果超過等待時間,則直接拋出JedisConnectionException;
    private static int MAX_WAIT = 3000;

    private static int TIMEOUT = 3000;

    //在borrow一個jedis實例時,是否提前進行validate操作;如果爲true,則得到的jedis實例均是可用的;
    private static boolean TEST_ON_BORROW = true;

    //在return給pool時,是否提前進行validate操作;
    private static boolean TEST_ON_RETURN = true;

    private static JedisPool jedisPool = null;

    /**
     * redis過期時間,以秒爲單位
     */
    public final static int EXRP_HOUR = 60 * 60; //一小時
    public final static int EXRP_DAY = 60 * 60 * 24; //一天
    public final static int EXRP_MONTH = 60 * 60 * 24 * 30; //一個月

    /**
     * 初始化Redis連接池
     */
    private static void initialPool() {
        try {
            JedisPoolConfig config = new JedisPoolConfig();
            config.setMaxTotal(MAX_ACTIVE);
            config.setMaxIdle(MAX_IDLE);
            config.setMaxWaitMillis(MAX_WAIT);
            config.setTestOnBorrow(TEST_ON_BORROW);

            jedisPool = new JedisPool(config, IP, PORT, TIMEOUT, "123456");
        } catch (Exception e) {
            //logger.error("First create JedisPool error : "+e);
            e.getMessage();
        }
    }


    /**
     * 在多線程環境同步初始化
     */
    private static synchronized void poolInit() {
        if (jedisPool == null) {
            initialPool();
        }
    }


    /**
     * 同步獲取Jedis實例
     *
     * @return Jedis
     */
    public synchronized static Jedis getJedis() {
        if (jedisPool == null) {
            poolInit();
        }
        Jedis jedis = null;
        try {
            if (jedisPool != null) {
                jedis = jedisPool.getResource();
            }
        } catch (Exception e) {
            e.getMessage();
            // logger.error("Get jedis error : "+e);
        }
        return jedis;
    }


    /**
     * 釋放jedis資源
     *
     * @param jedis
     */
    public static void returnResource(final Jedis jedis) {
        if (jedis != null && jedisPool != null) {
            jedisPool.returnResource(jedis);
        }
    }

    public static Long sadd(String key, String... members) {
        Jedis jedis = null;
        Long res = null;
        try {
            jedis = getJedis();
            res = jedis.sadd(key, members);
        } catch (Exception e) {
            //logger.error("sadd  error : "+e);
            e.getMessage();
        }
        return res;
    }
}
    public class RedisLock {

    private static final int setnxSuccss = 1;

    /**
     * 獲取鎖
     *
     * @param lockKey        定義鎖的key
     * @param notLockTimeOut 沒有獲取鎖的超時時間
     * @param lockTimeOut    使用鎖的超時時間
     * @return
     */
    public String getLock(String lockKey, int notLockTimeOut, int lockTimeOut) {
        // 獲取Redis連接
        Jedis jedis = RedisUtil.getJedis();
        // 定義沒有獲取鎖的超時時間
        Long endTimeOut = System.currentTimeMillis() + notLockTimeOut;
        while (System.currentTimeMillis() < endTimeOut) {
            String lockValue = UUID.randomUUID().toString();
            // 如果在多線程情況下誰能夠setnx 成功返回0 誰就獲取到鎖
            if (jedis.setnx(lockKey, lockValue) == setnxSuccss) {
                jedis.expire(lockKey, lockTimeOut / 1000);
                return lockValue;
            }
            // 否則情況下 在超時時間內繼續循環
        }
        try {
            if (jedis != null) {
                jedis.close();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 釋放鎖 其實就是將該key刪除
     *
     * @return
     */
    public Boolean unLock(String lockKey, String lockValue) {
        Jedis jedis = RedisUtil.getJedis();
        // 確定是對應的鎖 ,才刪除
        if (lockValue.equals(jedis.get(lockKey))) {
            return jedis.del(lockKey) > 0 ? true : false;
        }
        return false;
    }
}
public class TestService {

    private RedisLock redisLock = new RedisLock();
    private String lockKey = "redis_lock";

    public void service() {
        // 1.獲取鎖
        String lockValue = redisLock.getLock(lockKey, 5000, 5000);
        if (StringUtils.isEmpty(lockValue)) {
            System.out.println(Thread.currentThread().getName() + ",獲取鎖失敗!");
            return;
        }
        // 2.獲取鎖成功執行業務邏輯
        System.out.println(Thread.currentThread().getName() + ",獲取成功,lockValue:" + lockValue);
        // 3.釋放lock鎖
        redisLock.unLock(lockKey, lockValue);
    }
}
發佈了104 篇原創文章 · 獲贊 35 · 訪問量 5萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章