redis 加鎖與解鎖的詳細總結,解決線程並發導致臟數據


1.前言

對每個controller來說都是全新且單獨的,原因是多線程,如果多個請求操作共有的數據,這樣的並發操作會導致臟數據

怎么解決?

mysql可以使用積極鎖解決,

這里講解的是redis的解決辦法,雖然有幾種解決辦法,但我這里只記錄最好的:setnx指令算法加鎖,思路與mysql的消極鎖相似

2.redis鎖需要滿足幾個要求:

(1)只能讓一個客戶端加鎖,當鎖存在時其他客戶端不可以加鎖

(2)只能讓加鎖的客戶端解鎖,不允許其他客戶端解鎖

(3)當鎖存在時,加鎖失敗的客戶端需要等待解鎖后自己加鎖,只有自己加鎖成功后才可以操作共有數據,即阻塞操作

(4)不能產生死鎖,如果加鎖的客戶端還沒有解鎖前就因為某些原因就崩潰了,鎖自動解鎖,不影響其他客戶端操作

3.原理

(1)加鎖 則是 使用setnx指令,新建一個鍵值對,如果成功會返回 OK ,即視為加鎖成功,如果已經存在,則返回空 ,視為加鎖失敗

(2)為了確保不產生死鎖,應該對這個數據設置存活時間,如果崩潰后,到時間會自動刪除

(3)解鎖 則是 使用Lua腳本語句對鍵值對判斷這個鎖是不是自己加的,如果時別人的或者不存在,則不操作,如果是自己的則做刪除鍵值對操作

(4)無論時加鎖還是解鎖操作,為了確保邏輯的原子性,都應該是個多參數操作指令,有些低版本的redis不支持,才會將指令拆分成多條順序執行

   但是容易導致邏輯問題,形成臟數據,

4.封裝工具

我做的一個工具類,輸入參數即可完成加鎖 解鎖操作 ,這是單機的,如果是分布式則改將jedis對象改成分布式對象ShardedJedisPool即可,思路一樣

package cn.cen2guo.clinic.redis;

import redis.clients.jedis.Jedis;

import java.util.Collections;

public class JedisLock {
    //加鎖檢查
    //加鎖標識,返回結果是這個則說明加鎖成功
    private static final String LOCK_SUCCESS = "OK";
    //定義set方法的使用方式,如果不存在則以 lockKey 為 key ,requestId 為value 新建string類型鍵值 ,
    private static final String SET_IF_NOT_EXIST = "NX";
    //表示開啟存活時間,PX是毫秒數 ,如果想以秒為單位則設為EX
    private static final String SET_WITH_EXPIRE_TIME = "PX";
    /**
     * 嘗試獲取鎖[加鎖]
     *
     * @param jedis      Redis客戶端
     * @param lockKey    鎖
     * @param requestId  請求標識
     * @param expireTime 超期時間
     * @return 是否獲取成功
     */
    public static boolean tryGetDistributedLock(Jedis jedis, String lockKey, String requestId, int expireTime) {
        //語句意思是,判斷lockKey 這個key是否存在,不存在則以 lockKey 為 key ,requestId 為value 新建string類型鍵值 ,
        //並開啟存活時間,單位毫秒,
        //新建成功返回結果 OK ,說明加鎖成功
        // 失敗則為空,說明加鎖失敗
        String result = jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime);
        if (LOCK_SUCCESS.equals(result)) {
            return true;
        }
        return false;
    }

    //
    //
    //
    //
    //
    //
    //
    //
    //

    //解鎖標識,返回結果是這個則說明解鎖成功
    private static final Long RELEASE_SUCCESS = 1L;
    /**
     * 釋放鎖
     * * @param jedis Redis客戶端
     * * @param lockKey 鎖
     * * @param requestId 請求標識
     * * @return 是否釋放成功
     */
    public static boolean releaseDistributedLock(Jedis jedis, String lockKey, String requestId) {
//        使用Lua腳本,執行判斷語句,
        //刪除成功則返回結果1L,失敗則為0
        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));
        System.out.println("解鎖結果是=="+result);
        if (RELEASE_SUCCESS.equals(result)) {
            return true;
        }
        return false;
    }
}
JedisLock

5.使用

加鎖

    /**
             * 給等待池加鎖
             */
            //鎖的key名,自定義
            String lockKey = "lock" ;
            //唯一識別是哪個用戶端的鎖id
//            標准的UUID格式為:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx (8-4-4-4-12)。
            //時間戳+隨機數
            String requestId = System.currentTimeMillis() + UUID.randomUUID().toString();
            //存活時間,10秒,防止崩潰造成死鎖
            int expireTime = 10000;
            //獲取jedis對象
            Jedis jedis = jedisMyGetJedis.myGetJedis();
            //加鎖操作
            boolean f = JedisLock.tryGetDistributedLock(jedis, lockKey, requestId, expireTime);
            if (!f) {
//                失敗,鎖已經存在
                //休眠0.5秒后再次加鎖操作
                System.out.println("加鎖失敗,鎖已經存在" + new Date());
                int k = 0;
                while (k == 0) {
                    System.out.println("睡眠一次" + new Date());
                    //當前線程休眠0.5秒
                    Thread.sleep(500);
                    boolean f2 = JedisLock.tryGetDistributedLock(jedis, lockKey, requestId, expireTime);
                    if (f2) {
                        //加鎖成功
                        // 退出循環
                        k = 1;
                    }
                }
            }
            //加鎖成功
            System.out.println("加鎖成功" + new Date());
            //下面的操作只會有一個人操作,因此不需要擔心有並發操作
View Code

解鎖

  //解鎖
                boolean r = JedisLock.releaseDistributedLock(jedis, lockKey, requestId);
                if (r) {
                    System.out.println("解鎖成功");
                } else {
                    System.out.println("解鎖失敗");
                }
                //關閉jedis對象
                jedis.close();
View Code

 


免責聲明!

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



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