1. 程式人生 > 其它 >redis分散式鎖實現與思考

redis分散式鎖實現與思考

分散式鎖

  • 說明:
    在java中我們最常使用的加鎖方式就是 synchronized關鍵字和各種 Lock鎖,但是這種方式加的鎖只能保證在單專案或者說同一個jvm中起作用.但是在現在的分散式環境下就不能很好的應對分散式環境的加鎖需求,所以有了分散式鎖
  • 分散式鎖:
    分散式鎖就是一種思想,指的是能在分散式環境中,在多個地方使用一個鎖的時候,保證只能同時有一個持鎖物件.一般是加鎖的地方由常規的java的鎖,變成 第三方元件或工具實現(比如:redis,memcached,基於資料庫的實現,zookeeper 等等)

redis 的實現

一般過程分為:加鎖,解鎖

  • 加鎖:
    一般是通過 NX(即當key不存在時,我們進行set操作;若key已經存在,則不做任何操作) 命令,設定 一個key (一般會以為某個系統用的引數) ,和一個隨機字串(即設定的key對應的value),和(PX)超時時間, 命令如下:
    SET key value NX PX 2000
  • 解鎖
    解鎖的過程就是安全刪除這個key的過程,通過對當前的鎖持有的字串和 redis中的key的鎖值進行匹配,如果配對則可以移除;或者超過超時時間,鎖也會自動退出被其他執行緒使用
  • 環境
    使用的是 jedis 2.9.0
    <dependency>
       <groupId>redis.clients</groupId>
       <artifactId>jedis</artifactId>
       <version>2.9.0</version>
    </dependency>
    

程式碼實現

說明:一般設定分散式鎖的時候,比如:金額操作會在多個地方,使用同一個key比如使用者賬號,這個時候如果沒獲取到鎖,肯定不如直接返回報錯或異常或者直接結束,應該有個重試競爭機制

/**
     * @param jedis             jedis 連線
     * @param lockKey           傳入的鎖標識
     * @param tryTimeoutMs      嘗試獲取鎖的時間
     * @param lockTimeoutMS     鎖的超時時間
     * @return                  返回設定key對應的value值
     */
    public static String lock(Jedis jedis,String lockKey, long tryTimeoutMs, int lockTimeoutMS) {
        String retTip = null;
        String identifier = UUID.randomUUID().toString();
        try {
            long end = System.currentTimeMillis() + tryTimeoutMs;
            while (System.currentTimeMillis() < end) {
                String result = jedis.set(lockKey, identifier, "NX", "PX", lockTimeoutMS);
                if ("OK".equals(result)) {
                    retTip = identifier;
                    break;
                }
                try {
                    Thread.sleep(10);
                } catch (InterruptedException ie) {
                    Thread.currentThread().interrupt();
                }
            }
        } catch (Exception e) {
            return "err";
        } finally {
            // 異常釋放連線
            if (jedis != null) {
                jedis.close();
            }
        }
        return retTip;
    }
  • 重試呼叫和釋放鎖的例子
// 呼叫,迴圈多次嘗試獲取鎖
	public static void test(String[] args) {
        String key = "xxx";// key 標識
        try {
            // 進行對應的操作
            int cnt=0;
            while(true) {
                cnt++;
                if (cnt > 5) {
                    throw new RuntimeException();
                }
                String resTip = lock(jedisPool.getResource(),key, 6000, 5000);
                if (!org.springframework.util.StringUtils.isEmpty(resTip)) {
                    if ("err".equals(resTip)) {//redis 出錯
                        throw new RuntimeException();// 異常退出
                    }
                    // 獲取鎖之後的操作
                    releaseLock(key, resTip);
                    break;
                }
            }
        }catch (Exception e) {
            e.printStackTrace();
        }
    }



	private static final Long RELEASE_SUCCESS = 1L;

        /**
         * 釋放分散式鎖
         * @param jedis Redis客戶端
         * @param lockKey 鎖 key
         * @param requestId 請求標識 value
         * @return 是否釋放成功
         */
    public static boolean releaseLock(Jedis jedis, String lockKey, String requestId) {

        String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
        Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(requestId));

        if (RELEASE_SUCCESS.equals(result)) {
            return true;
        }
        return false;

    }