redis分佈式鎖實現與思考

分佈式鎖

  • 說明:
    在java中咱們最常使用的加鎖方式就是 synchronized關鍵字和各類 Lock鎖,可是這種方式加的鎖只能保證在單項目或者說同一個jvm中起做用.可是在如今的分佈式環境下就不能很好的應對分佈式環境的加鎖需求,因此有了分佈式鎖
  • 分佈式鎖:
    分佈式鎖就是一種思想,指的是能在分佈式環境中,在多個地方使用一個鎖的時候,保證只能同時有一個持鎖對象.通常是加鎖的地方由常規的java的鎖,變成 第三方組件或工具實現(好比:redis,memcached,基於數據庫的實現,zookeeper 等等)

redis 的實現

通常過程分爲:加鎖,解鎖java

  • 加鎖:
    通常是經過 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好比用戶帳號,這個時候若是沒獲取到鎖,確定不如直接返回報錯或異常或者直接結束,應該有個重試競爭機制redis

/**
     * @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;

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