RedisTemplate用SETNX命令實現分布式鎖


使用SETNX命令獲取分布式鎖的步驟:

  • C1和C2線程同時檢查時間戳獲取鎖,執行SETNX命令並都返回0,此時鎖仍被C3持有,並且C3已經崩潰
  • C1 DEL
  • C1 使用SETNX命令獲取鎖,並且成功
  • C2 DEL
  • C2 使用SETNX命令獲取鎖,並且成功
  • ERROR : 由於競態條件,C1和C2都獲取到了鎖

幸運的是,以下面的步驟完全可以避免這種情況發生,看看C4線程如何操作

  • C4使用SETNX命令獲取鎖
  • C3已經崩潰但是仍然持有鎖,所以Redis返回0給C4
  • C4使用GET命令獲取鎖並檢查鎖是否已經過期,如果沒有過期,則繼續等待一段時間並重新重試
  • 如果鎖已經過期,C4嘗試 GETSET lock.foo <current Unix timestamp + lock timeout + 1>
  • 利用GETSET語法,C4可以檢查舊時間是否仍然是過期時間,如果是,則獲取鎖
  • 如果另一個客戶端C5率先獲取到鎖,C4執行GETSET命令后將返回非過期時間,然后C4繼續從頭開始重新嘗試獲取鎖。此操作C4將延長一點C5獲取到的鎖的過期時間,不過這不是什么大問題。

本次demo使用的是 Spring Boot 進行的單元測試;歡迎討論;

package com.example.demo.controller;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.test.context.junit4.SpringRunner;

import java.util.Objects;

/**
 * Description: demo1 <br>
 *
 * @author Liang lp
 * Date: 2019/12/13 16:41 <br>
 */
@SpringBootTest
@RunWith(SpringRunner.class)
public class demo1 {

    private static final Logger log = LoggerFactory.getLogger(demo1.class);

    //鎖名稱前綴
    public static final String LOCK_PREFIX = "redis_lock";
    //加鎖失效時間,毫秒
    public static final int LOCK_EXPIRE = 600; // ms

    @Autowired
    RedisTemplate redisTemplate;

    /**
     * 使用進行測試
     */
    @Test
    public void demo() {
        //設置個Key
        String key = "order_id";
        try {
            boolean b = lock(LOCK_PREFIX + key);
            if (b) {
                log.info("開始執行業務邏輯");
                Thread.sleep(2000);
            } else {
                log.info("獲取鎖錯誤{}", b);
                return;
            }
        } catch (Exception e) {
            log.info("獲取鎖異常{}", e);
        } finally {
            //刪除鎖;
            deleteLock(LOCK_PREFIX + key);
        }
    }

    /**
     * 獲得鎖
     *
     * @param lock
     * @return
     */
    public boolean lock(String lock) {
        return (boolean) redisTemplate.execute((RedisCallback) connection -> {
            //獲取時間毫秒值
            long expireAt = System.currentTimeMillis() + LOCK_EXPIRE + 1;
            //獲取鎖
            Boolean acquire = connection.setNX(lock.getBytes(), String.valueOf(expireAt).getBytes());
            if (acquire) {
                return true;
            } else {
                byte[] bytes = connection.get(lock.getBytes());
                //非空判斷
                if (Objects.nonNull(bytes) && bytes.length > 0) {
                    long expireTime = Long.parseLong(new String(bytes));
                    // 如果鎖已經過期
                    if (expireTime < System.currentTimeMillis()) {
                        // 重新加鎖,防止死鎖
                        byte[] set = connection.getSet(lock.getBytes(), String.valueOf(System.currentTimeMillis() + LOCK_EXPIRE + 1).getBytes());
                        return Long.parseLong(new String(set)) < System.currentTimeMillis();
                    }
                }
            }
            return false;
        });
    }


    /**
     * 刪除鎖
     *
     * @param key
     */
    public void deleteLock(String key) {
        redisTemplate.delete(key);
    }
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM