利用redis實現分布式鎖

RegPoate 8年前發布 | 5K 次閱讀 Java Redis

SETNX  并不難完美實現(不帶過期時間),SETNX 實現鎖有陷阱需謹慎
SETEX  復寫,帶過期時間(原子)
</div>

 

分布式鎖工具    

  private static Logger logger = Logger.getLogger(LockUtils.class);
       /**
     * 最長時間鎖為1天
     */
    private final static int maxExpireTime = 24 * 60 * 60;

    /**
     * 系統時間偏移量15秒,服務器間的系統時間差不可以超過15秒,避免由于時間差造成錯誤的解鎖
     */
    private final static int offsetTime = 15;

    /**
     * 鎖只是為了解決小概率事件,最好的方式是不用,從設計上避免分布式鎖
     * 
     * @param key
     *            key
     * @param value
     * @param waitTime
     *            秒 - 最大等待時間,如果還無法獲取,則直接失敗
     * @param expire
     *            秒- 鎖生命周期時間
     * @return true 成功 false失敗
     * @throws Exception
     */
    public static boolean Lock(String key, String value, int waitTime, int expire) {

        long start = System.currentTimeMillis();
        String lock_key = key + "_lock";
        logger.info("開始獲取分布式鎖 key:" + key + " lock_key:" + lock_key + " value:" + value);

        do {
            try {
                Thread.sleep(1);
                long ret = CacheUtils.Setnx(CacheSpacePrefixEnum.TOOLBAR_SYS.name(), lock_key, System.currentTimeMillis() + "$T$" + value,
                                            (expire > maxExpireTime) ? maxExpireTime : expire);
                if (ret == 1) {
                    logger.info("成功獲得分布式鎖 key:" + key + " value:" + value);
                    return Boolean.TRUE;
                } else { // 存在鎖,并對死鎖進行修復
                    String desc = CacheUtils.GSetnx(CacheSpacePrefixEnum.TOOLBAR_SYS.name(), lock_key);

                    // 首次鎖檢測
                    if (desc.indexOf("$T$") > 0) {
                        // 上次鎖時間
                        long lastLockTime = NumberUtils.toLong(desc.split("[$T$]")[0]);
                        // 明確死鎖,利用Setex復寫,再次設定一個合理的解鎖時間讓系統正常解鎖
                        if (System.currentTimeMillis() - lastLockTime > (expire + offsetTime) * 1000) {
                            // 原子操作,只需要一次,【任然會發生小概率事件,多個服務同時發現死鎖同時執行此行代碼(并發),
                            // 為什么設置解鎖時間為expire(而不是更小的時間),防止在解鎖發送錯亂造成新鎖解鎖】
                            CacheUtils.Setex(CacheSpacePrefixEnum.TOOLBAR_SYS.name(), lock_key, value, expire);
                            logger.warn("發現死鎖【" + expire + "秒后解鎖】key:" + key + " desc:" + desc);
                        } else {
                            logger.info("當前鎖key:" + key + " desc:" + desc);
                        }
                    } else {
                        logger.warn("死鎖解鎖中key:" + key + " desc:" + desc);
                    }

                }
                if (waitTime == 0) {
                    break;
                }
                Thread.sleep(500);
            }
            catch (Exception ex) {
                logger.error(Trace.GetTraceStackDetails("獲取鎖失敗", ex));
            }
        }
        while ((System.currentTimeMillis() - start) < waitTime * 1000);
        logger.warn("獲取分布式鎖失敗 key:" + key + " value:" + value);
        return Boolean.FALSE;
    }

    /**
     * 解鎖
     * 
     * @param key
     * @return
     * @throws Exception
     */
    public static boolean UnLock(String key) {
        String lock_key = key + "_lock";
        try {
            CacheUtils.Del(CacheSpacePrefixEnum.TOOLBAR_SYS.name(), lock_key);
        }
        catch (Exception ex) {
            logger.error(Trace.GetTraceStackDetails("解鎖鎖失敗key:" + key + " lock_key:" + lock_key, ex));
        }
        return Boolean.FALSE;
    }

redis操作分裝部分    

  @Override
    public Long Setnx(String key, String value, int expireTime) throws Exception {
        ShardedJedis jedis = null;

        try {
            jedis = pool.getResource();

            Long ret = jedis.setnx(key, value);
            if (ret == 1 && expireTime > 0) {
                jedis.expire(key, expireTime);
            }
            return ret;
        }
        catch (Exception e) {
            throw e;
        }
        finally {
            if (pool != null && jedis != null) {
                pool.returnResourceObject(jedis);
            }
        }
    }

    @Override
    public String Setex(String key, String value, int expireTime) throws Exception {
        ShardedJedis jedis = null;

        try {
            jedis = pool.getResource();


            String ret = jedis.setex(key, expireTime, value);
            return ret;
        }
        catch (Exception e) {
            throw e;
        }
        finally {
            if (pool != null && jedis != null) {
                pool.returnResourceObject(jedis);
            }
        }

    }

    @Override
    public String GSetnx(String key) throws Exception {
        ShardedJedis jedis = null;

        try {
            jedis = pool.getResource();
            return jedis.get(key);
        }
        catch (Exception e) {
            throw e;
        }
        finally {
            if (pool != null && jedis != null) {
                pool.returnResourceObject(jedis);
            }
        }
    }
 本文由用戶 RegPoate 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!