1. 程式人生 > 實用技巧 >Redis鎖實現防重複提交和併發問題

Redis鎖實現防重複提交和併發問題

@Slf4j
@Component
public class RedisLock {

    public static final int LOCK_EXPIRE = 5000;

    @Autowired
    private StringRedisTemplate redisTemplate;


    /**
     *  分散式鎖
     *
     * @param key key值
     * @return 是否獲取到
     */
    public boolean lock(String key) {

        String lock = key;
        try {
            return (Boolean) redisTemplate.execute((RedisCallback) connection -> {
                long expireAt = System.currentTimeMillis() + LOCK_EXPIRE;
                Boolean acquire = connection.setNX(lock.getBytes(), String.valueOf(expireAt).getBytes());
                if (acquire) {
                    log.info("使用者 [{}]加鎖成功",key);

                    return true;
                } else {
                    log.info("使用者 [{}]加鎖失敗",key);

                    //判斷該key上的值是否過期了
                    byte[] value = connection.get(lock.getBytes());
                    if (Objects.nonNull(value) && value.length > 0) {
                        long expireTime = Long.parseLong(new String(value));
                        if (expireTime < System.currentTimeMillis()) {
                            // 如果鎖已經過期
                            byte[] oldValue = connection.getSet(lock.getBytes(), String.valueOf(System.currentTimeMillis() + LOCK_EXPIRE).getBytes());
                            // 防止死鎖
                            return Long.parseLong(new String(oldValue)) < System.currentTimeMillis();
                        }
                    }
                }
                return false;
            });
        } finally {
            RedisConnectionUtils.unbindConnection(redisTemplate.getConnectionFactory());
        }
    }



    /**
     * 刪除鎖
     *
     * @param key
     */
    public void delete(String key) {
        try {
            redisTemplate.delete(key);
        } finally {
            RedisConnectionUtils.unbindConnection(redisTemplate.getConnectionFactory());
        }
    }

//實現業務

        try{
            // 判斷是否獲取了鎖
            boolean getLock = redisLock(lockKey);
            if(getLock){
                // 此處可以開始寫需要實現的程式碼
             
            }
        }catch(Exception e){
      e.printStackTrace();
}finally { 
// 判斷是否超時了,如果未超時,則釋放鎖。 超時了,鎖有可能被其他執行緒拿走了,就不做任何操作
redisLock.delete(String.valueOf(caronwerId));
}
}

知識點: redis Setnx(SETifNot eXists) 命令在指定的 key 不存在時,為 key 設定指定的值。

設定成功,返回 1 。 設定失敗,返回 0 。

redis> EXISTS job                # job 不存在
(integer) 0

redis> SETNX job "programmer"    # job 設定成功
(integer) 1

redis> SETNX job "code-farmer"   # 嘗試覆蓋 job ,失敗
(integer) 0

redis> GET job                   # 沒有被覆蓋
"programmer"

一.redis命令講解: 
setnx()命令: 
setnx的含義就是SET if Not Exists,其主要有兩個引數 setnx(key, value)。

該方法是原子的,如果key不存在,則設定當前key成功,返回1;如果當前key已經存在,則設定當前key失敗,返回0。

get()命令: 
get(key) 獲取key的值,如果存在,則返回;如果不存在,則返回nil; 
getset()命令: 
這個命令主要有兩個引數 getset(key, newValue)。該方法是原子的,對key設定newValue這個值,並且返回key原來的舊值。 
假設key原來是不存在的,那麼多次執行這個命令,會出現下邊的效果: 
1. getset(key, “value1”) 返回nil 此時key的值會被設定為value1 
2. getset(key, “value2”) 返回value1 此時key的值會被設定為value2 
3. 依次類推! 
二.具體的使用步驟如下: 
1. setnx(lockkey, 當前時間+過期超時時間) ,如果返回1,則獲取鎖成功;如果返回0則沒有獲取到鎖,轉向2。 
2. get(lockkey)獲取值oldExpireTime ,並將這個value值與當前的系統時間進行比較,如果小於當前系統時間,則認為這個鎖已經超時,可以允許別的請求重新獲取,轉向3。 
3. 計算newExpireTime=當前時間+過期超時時間,然後getset(lockkey, newExpireTime) 會返回當前lockkey的值currentExpireTime。 
4. 判斷currentExpireTime與oldExpireTime 是否相等,如果相等,說明當前getset設定成功,獲取到了鎖。如果不相等,說明這個鎖又被別的請求獲取走了,那麼當前請求可以直接返回失敗,或者繼續重試。 
5. 在獲取到鎖之後,當前執行緒可以開始自己的業務處理,當處理完畢後,比較自己的處理時間和對於鎖設定的超時時間,如果小於鎖設定的超時時間,則直接執行delete釋放鎖;如果大於鎖設定的超時時間,則不需要再鎖進行處理