中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

怎么使用redis分布式鎖

發布時間:2021-11-12 13:36:26 來源:億速云 閱讀:162 作者:iii 欄目:開發技術

本篇內容介紹了“怎么使用redis分布式鎖”的有關知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領大家學習一下如何處理這些情況吧!希望大家仔細閱讀,能夠學有所成!

    1. redis在實際的應用中

    不僅可以用來緩存數據,在分布式應用開發中,經常被用來當作分布式鎖的使用,為什么要用到分布式鎖呢?

    在分布式的開發中,以電商庫存的更新功能進行講解,在實際的應用中相同功能的消費者是有多個的,假如多個消費者同一時刻要去消費一條數據,假如業務邏輯處理邏輯是查詢出redis中的商品庫存,而如果第一個進來的消費的消費者獲取到庫存了,還沒進行減庫存操作,相對晚來的消費者就獲取了商品的庫存,這樣就導致數據會出錯,導致消費的數據變多了。

    例如:消費者A和消費者B分別去消費生產者C1和生產者C2的數據,而生產者都是使用同一個redis的數據庫的,如果生產者C1接收到消費者A的消息后,先進行查詢庫存,然后當要進行減庫存的時候,因為生產者C2接收到消費者B的消息后,也去查詢庫存,而因為生產者C1還沒有進行庫存的更新,導致生產者C2獲取到的庫存數是臟數據,而不是生產者C1更新后的數據,導致業務出錯。

    怎么使用redis分布式鎖

    如果不是分布式的應用,可以使用synchronized進行防止庫存更新的問題的產生,但是synchronized只是基于JVM層面的,如果在不同的JVM中,就不能實現這樣的功能。

       @GetMapping("getInt0")
        public String test() {
            synchronized (this) {
                //獲取當前商品的數量
                int productNum = Integer.parseInt(stringRedisTemplate.opsForValue().get("product"));
                //然后對商品進行出庫操作,即進行減1
                /*
                 * a業務邏輯
                 *
                 * */
                if (productNum > 0) {
                    stringRedisTemplate.opsForValue().set("product", String.valueOf(productNum - 1));
                    int productNumNow = productNum - 1;
                } else {
                    return "product=0";
                }
                int productNumNow = productNum - 1;
                return "success=" + productNumNow;
            }
        }

    2.如何使用redis的功能進行實現分布式鎖

    2.1 redis分布式鎖思想

    如果對redis熟悉的話,我們能夠想到redis中具有setnx的命令,該命令的功能宇set功能類似,但是setnx的命令在進行存數據前,會檢查redis中是否已經存在相同的key,如存在的話就返回false,反之則返回true,因此我們可以使用該命令的功能,設計一個分布式鎖。

    2.1.1設計思想:
    • 在請求相同功能的接口時,使用redis的setnx命令,如果使用setnx命令后返回的是為true,說明此時沒有其他的調用這個接口,就相當于獲取到鎖了,然后就可以繼續執行接下來的業務邏輯了。當執行完業務邏輯后,在返回數據前,就把key刪除了,然后其他的請求就能獲取到鎖了。

    • 如果使用setnx命令,返回的是false,說明此時有其他的消費者正在調用這個接口,因此需要等待其他消費者順利消費完成后,才能獲取到分布式的鎖。

    2.1.2 根據上面的設計思想進行代碼實現

    代碼片段【1】

      @GetMapping("getInt1")
        public String fubushisuo(){
            //setIfAbsent的指令功能和redis命令中的setNx功能一樣,如果redis中已經存在相同的key,則返回false
            String lockkey = "yigehaimeirumengdechengxuyuan";
            String lockvalue = "yigehaimeirumengdechengxuyuan";
            boolean opsForSet = stringRedisTemplate.opsForValue().setIfAbsent(lockkey,lockvalue);
            //如果能夠成功的設置lockkey,這說明當前獲取到分布式鎖
            if (!opsForSet){
                return "false";
            }
            //獲取當前商品的數量
            int productNum = Integer.parseInt(stringRedisTemplate.opsForValue().get("product"));
            //然后對商品進行出庫操作,即進行減1
            /*
            * a業務邏輯
            *
            * */
            if (productNum>0){
                stringRedisTemplate.opsForValue().set("product", String.valueOf(productNum - 1));
                int productNumNow = productNum - 1;
            }else {
                return "product=0";
            }
            //然后進行釋放鎖
            stringRedisTemplate.delete(lockkey);
            int productNumNow = productNum-1;
            return "success="+productNumNow;
        }
    2.1.2.1反思代碼片段【1】

    如果使用這種方式,會產生死鎖的方式:
    死鎖發生的情況:
    (1) 如果在a業務邏輯出現錯誤時,導致不能執行delete()操作,使得其他的請求不能獲取到分布式鎖,業務lockkey一直存在于reids中,導致setnx操作一直失敗,所以不能獲取到分布式鎖
    (2) 解決方法,使用對業務代碼進行try…catch操作,如果出現錯誤,那么使用finally對key進行刪除

    優化代碼【2】

     @GetMapping("getInt2")
        public String fubushisuo2(){
            //setIfAbsent的指令功能和redis命令中的setNx功能一樣,如果redis中已經存在相同的key,則返回false
            String lockkey = "yigehaimeirumengdechengxuyuan";
            String lockvalue = "yigehaimeirumengdechengxuyuan";
            boolean opsForSet = stringRedisTemplate.opsForValue().setIfAbsent(lockkey,lockvalue);
            int productNumNow = 0;
            //如果能夠成功的設置lockkey,這說明當前獲取到分布式鎖
            if (!opsForSet){
                return "false";
            }
            try {
                //獲取當前商品的數量
                int productNum = Integer.parseInt(stringRedisTemplate.opsForValue().get("product"));
                //然后對商品進行出庫操作,即進行減1
                /*
                 * b業務邏輯
                 * */
                if (productNum>0){
                    stringRedisTemplate.opsForValue().set("product", String.valueOf(productNum - 1));
                    productNumNow = productNum-1;
                }else {
                    return "product=0";
                }
    
            }catch (Exception e){
                System.out.println(e.getCause());
            }finally {
                    //然后進行釋放鎖
                stringRedisTemplate.delete(lockkey);
            }
    
            return "success="+productNumNow;
        }
    2.1.2.2反思代碼【2】

    出現問題的情況:
    如果這種情況也有會產生的情況,如果此時有多臺服務器都在運行該方法,
    其中有一個方法獲取到了分布式鎖,而在運行下面的業務代碼時,此時該服務器突然宕機了,導致其他的不能獲取到分布式鎖,

    解決方法:加上過期時間,但又服務宕機了,過了設置的時間后,redis會可以把key給刪除,這樣其他的的服務器就可以正常的進行上鎖了。

    優化代碼【3】

     @GetMapping("getInt3")
        public String fubushisuo3(){
            //setIfAbsent的指令功能和redis命令中的setNx功能一樣,如果redis中已經存在相同的key,則返回false
            String lockkey = "yigehaimeirumengdechengxuyuan";
            String lockvalue = "yigehaimeirumengdechengxuyuan";
           //[01] boolean opsForSet = stringRedisTemplate.opsForValue().setIfAbsent(lockkey,lockvalue);
            //設置過期時間為10秒,但是如果使用該命令,沒有原子性,可能執行expire前宕機了,而不是設置過期時間,
           //[02] stringRedisTemplate.expire(lockkey, Duration.ofSeconds(10));
            //使用setIfAbsent(lockkey,lockvalue,10,TimeUnit.SECONDS);代碼代替上面[01],[02]行代碼
            Boolean opsForSet = stringRedisTemplate.opsForValue().setIfAbsent(lockkey, lockvalue, 10, TimeUnit.SECONDS);
            int productNumNow = 0;
            //如果能夠成功的設置lockkey,這說明當前獲取到分布式鎖
            if (!opsForSet){
                return "false";
            }
            try {
                //獲取當前商品的數量
                int productNum = Integer.parseInt(stringRedisTemplate.opsForValue().get("product"));
                //然后對商品進行出庫操作,即進行減1
                /*
                 * c業務邏輯
                 * */
                if (productNum>0){
                    stringRedisTemplate.opsForValue().set("product", String.valueOf(productNum - 1));
                    productNumNow = productNum-1;
                }else {
                    return "product=0";
                }
    
            }catch (Exception e){
                System.out.println(e.getCause());
            }finally {
            
            //然后進行釋放鎖
                stringRedisTemplate.delete(lockkey);
            }
            
            return "success="+productNumNow;
        }
    2.1.2.3 反思優化代碼【3】

    出現問題的情況:
    如果c業務邏輯持續超過了設置時間,導致redis中的lockkey過期了,
    而其他的用戶此時訪問該方法時獲取到鎖了,而在此時,之前的的c業務邏輯也執行完成了,但是他會執行delete,把lcokkey刪除了。導致分布式鎖出錯。
    例子:在12:01:55的時刻,有一個A來執行該getInt3方法,并且成功獲取到鎖,但是A執行了10秒后還不能完成業務邏輯,導致redis中的鎖過期了,而在11秒的時候有B來執行getint3方法,因為key被A刪除了,導致B能夠成功的獲取redis鎖,而在B獲取鎖后,A因為執行完成了,然后把reids中的key給刪除了,但是我們注意的是,A刪除的鎖是B加上去的,而A的鎖是因為過期了,才被redis自己刪除了,因此這導致了C如果此時來時也能獲取redis分布式鎖

    解決方法:使用UUID,產生一個隨機數,當要進行delete(刪除)redis中key時,判斷是不是之前自己設置的UUID

    代碼優化【4】

      @GetMapping("getInt4")
        public String fubushisuo4(){
            //setIfAbsent的指令功能和redis命令中的setNx功能一樣,如果redis中已經存在相同的key,則返回false
            String lockkey = "yigehaimeirumengdechengxuyuan";
            //獲取UUID
            String lockvalue = UUID.randomUUID().toString();
            Boolean opsForSet = stringRedisTemplate.opsForValue().setIfAbsent(lockkey, lockvalue, 10, TimeUnit.SECONDS);
            int productNumNow = 0;
            //如果能夠成功的設置lockkey,這說明當前獲取到分布式鎖
            if (!opsForSet){
                return "false";
            }
            try {
                //獲取當前商品的數量
                int productNum = Integer.parseInt(stringRedisTemplate.opsForValue().get("product"));
                //然后對商品進行出庫操作,即進行減1
                /*
                 * c業務邏輯
                 * */
    
                if (productNum>0){
                    stringRedisTemplate.opsForValue().set("product", String.valueOf(productNum - 1));
                    productNumNow = productNum-1;
                }else {
                    return "product=0";
                }
    
            }catch (Exception e){
                System.out.println(e.getCause());
            }finally {
            
            //進行釋放鎖
                if (lockvalue==stringRedisTemplate.opsForValue().get(lockkey)){
                    stringRedisTemplate.delete(lockkey);
                }
            }
            return "success="+productNumNow;
        }
    2.1.2.4 反思優化代碼【4】

    出現問題的情況:
    此時該方法是比較完美的,一般并發不是超級大的情況下都可以進行使用,但是關于key的過期時間需要根據業務執行的時間,進行設置,防止在業務還沒執行完時,key就過期了.

    解決方法:目前有很多redis的分布式鎖的框架,其中redisson用的是比較多的

    2.2 使用redisson進行實現分布式鎖

    先添加redisson的maven依賴

    <dependency>
    			<groupId>org.redisson</groupId>
    			<artifactId>redisson</artifactId>
    			<version>3.11.1</version>
    		</dependency>

    redisson的bean配置

    @Configuration
    public class RedissonConfigure {
        @Bean
        public Redisson redisson(){
            Config config = new Config();
            config.useSingleServer().setAddress("redis://27.196.106.42:6380").setDatabase(0);
            return (Redisson) Redisson.create(config);
        }
    }

    實現分布式鎖代碼如下

     @GetMapping("getInt5")
        public String fubushisuo5(){
            //setIfAbsent的指令功能和redis命令中的setNx功能一樣,如果redis中已經存在相同的key,則返回false
            String lockkey = "yigehaimeirumengdechengxuyuan";
            //獲取UUID
            RLock lock = redisson.getLock(lockkey);
            lock.lock();
            int productNumNow = 0;
            //如果能夠成功的設置lockkey,這說明當前獲取到分布式鎖
    
            try {
                //獲取當前商品的數量
                int productNum = Integer.parseInt(stringRedisTemplate.opsForValue().get("product"));
                //然后對商品進行出庫操作,即進行減1
                /*
                 * c業務邏輯
                 * */
                if (productNum>0){
                stringRedisTemplate.opsForValue().set("product", String.valueOf(productNum - 1));
                productNumNow = productNum-1;
                }else {
                    return "product=0";
                }
            }catch (Exception e){
                System.out.println(e.getCause());
            }finally {
               lock.unlock();
                }
    
            //然后進行釋放鎖
            return "success="+productNumNow;
        }

    從面就能看到,redisson實現分布式鎖是非常簡單的,只要簡單的幾條命令就能實現分布式鎖的功能的。
    redisson實現分布式鎖的只要原理如下:
    redisson使用了Lua腳本語言使得命令既有原子性,redisson獲取鎖時,會給key設置30秒的過期是按,同時redisson會記錄當前請求的線程編號,然后定時的去檢查該線程的狀態,如果還處于執行狀態的話,而且key差不多要超期過時時,redisson會修改key的過期時間,一般增加10秒。這樣就可以動態的設置key的過期時間了,彌補了優化代碼【4】的片段

    “怎么使用redis分布式鎖”的內容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業相關的知識可以關注億速云網站,小編將為大家輸出更多高質量的實用文章!

    向AI問一下細節

    免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

    AI

    黔西县| 临猗县| 大埔区| 星子县| 远安县| 永清县| 德惠市| 师宗县| 揭阳市| 多伦县| 常州市| 扎囊县| 镇平县| 垦利县| 博乐市| 淮南市| 镇赉县| 扎囊县| 吴堡县| 墨竹工卡县| 安乡县| 新和县| 泾源县| 乐陵市| 南江县| 邳州市| 南木林县| 祁东县| 常德市| 塘沽区| 建始县| 柏乡县| 五峰| 齐齐哈尔市| 新昌县| 深州市| 岳西县| 旬邑县| 烟台市| 西藏| 威海市|