redis實現的鎖

須要考慮的事情有:java

  1. 釋放鎖的線程必定要與加鎖的線程一致! (生產實際案例: 多筆交易同時還同一筆借款還款計劃的不一樣期時,因併發,致使意外釋放鎖被非加鎖線程釋放。致使還款計劃出現前期還處理還款中,後期已經還款成功)
  2. 合理控制加鎖等待時間(網絡抖動形成鏈接redis服務異常)
  3. 若是獲取鎖失敗,須要明確鎖失敗的緣由( 如:鏈接超時、鎖被暫用、異常 等狀況), 合理肯定降級方案(本地緩存來作鎖(前提是同一個key的請求須要負載均衡到同一臺機器上) 或 按需返回true or false。)
  4. 合理設置鎖的自動失效時間
  5. 程序處理finally必定要主動釋放鎖
  6. 在加鎖和釋放鎖時要考慮原子性。 加鎖:SETNX ;釋放鎖: 執行script:
    if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end

     

加鎖時:redis

建立一個隨機數,經過redis 提供的SET_IF_NOT_EXIST方式寫入這個key。並保存隨機數在線程上下文中緩存

private static final String SET_IF_NOT_EXIST = "NX";
private static final String SET_WITH_EXPIRE_TIME = "PX";
private static final String LOCK_SUCCESS = "OK";
private static ThreadLocal<String> ownerThreadLocal = new ThreadLocal<>();

源碼以下:網絡

在釋放鎖時: 併發

經過執行腳本的方式保證在併發下的一致性! 只有當線程上下文對象中的值與redis保存的值一致時,才能刪掉這個key,釋放成功。 這樣能夠保證加鎖與釋放鎖的都是這個線程!負載均衡

private static final String UNLOCK_SCRIPT = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
   private static final long UNLOCK_SUCCESS = 1L;
public boolean unlock(String key) {
        String ownerId = ownerThreadLocal.get();
        if (StringUtils.isBlank(ownerId)) {
            return false;
        }

        try {
            List<String> keys = Collections.singletonList(key);
            //lua
            Long result = stringRedisTemplate.execute(new DefaultRedisScript<>(UNLOCK_SCRIPT, Long.class), keys, ownerId);
            if (UNLOCK_SUCCESS == result) {
                return true;
            }
        } catch (Exception e) {
            log.error("redis unlock fail", e);
        } finally {
            ownerThreadLocal.remove();
        }
        return false;
    }
相關文章
相關標籤/搜索