最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

詳解Redis分布式鎖的原理與實(shí)現(xiàn)

 更新時(shí)間:2022年06月23日 08:31:57   作者:溫故知新之java  
在單體應(yīng)用中,如果我們對共享數(shù)據(jù)不進(jìn)行加鎖操作,會出現(xiàn)數(shù)據(jù)一致性問題,我們的解決辦法通常是加鎖。下面我們一起聊聊使用redis來實(shí)現(xiàn)分布式鎖

前言

在單體應(yīng)用中,如果我們對共享數(shù)據(jù)不進(jìn)行加鎖操作,會出現(xiàn)數(shù)據(jù)一致性問題,我們的解決辦法通常是加鎖。在分布式架構(gòu)中,我們同樣會遇到數(shù)據(jù)共享操作問題,此時(shí),我們就需要分布式鎖來解決問題,下面我們一起聊聊使用redis來實(shí)現(xiàn)分布式鎖。

使用場景

  • 庫存超賣 比如 5個(gè)筆記本 A 看 準(zhǔn)備買3個(gè) B 買2個(gè) C 4個(gè) 一下單 3+2+4 =9
  • 防止用戶重復(fù)下單
  • MQ消息去重
  • 訂單操作變更

為什么要使用分布式鎖

從業(yè)務(wù)場景來分析,有一個(gè)共性,共享資源的競爭,比如庫存商品,用戶,消息,訂單等,這些資源在同一時(shí)間點(diǎn)只能有一個(gè)線程去操作,并且在操作期間,禁止其他線程操作。要達(dá)到這個(gè)效果,就要實(shí)現(xiàn)共享資源互斥,共享資源串行化。其實(shí),就是對共享資源加鎖的問題。在單應(yīng)用(單進(jìn)程多線程)中使用鎖,我們可以使用synchronize、ReentrantLock等關(guān)鍵字,對共享資源進(jìn)行加鎖。在分布式應(yīng)用(多進(jìn)程多線程)中,分布式鎖是控制分布式系統(tǒng)之間同步訪問共享資源的一種方式。

如何使用分布式鎖

流程圖

分布式鎖的狀態(tài)

  • 客戶端通過競爭獲取鎖才能對共享資源進(jìn)行操作
  • 當(dāng)持有鎖的客戶端對共享資源進(jìn)行操作時(shí)
  • 其他客戶端都不可以對這個(gè)資源進(jìn)行操作
  • 直到持有鎖的客戶端完成操作

分布式鎖的特點(diǎn)

互斥性

在任意時(shí)刻,只有一個(gè)客戶端可以持有鎖(排他性)

高可用,具有容錯(cuò)性

只要鎖服務(wù)集群中的大部分節(jié)點(diǎn)正常運(yùn)行,客戶端就可以進(jìn)行加鎖解鎖操作

避免死鎖

具備鎖失效機(jī)制,鎖在一段時(shí)間之后一定會釋放。(正常釋放或超時(shí)釋放)

加鎖和解鎖為同一個(gè)客戶端

一個(gè)客戶端不能釋放其他客戶端加的鎖了

分布式鎖的實(shí)現(xiàn)方式(以redis分布式鎖實(shí)現(xiàn)為例)

簡單版本

/**
 * 簡單版本
 * @author:liyajie
 * @createTime:2022/6/22 15:42
 * @version:1.0
 */
public class SimplyRedisLock {
    // Redis分布式鎖的key
    public static final String REDIS_LOCK = "redis_lock";

    @Autowired
    StringRedisTemplate template;

    public String index(){

        // 每個(gè)人進(jìn)來先要進(jìn)行加鎖,key值為"redis_lock",value隨機(jī)生成
        String value = UUID.randomUUID().toString().replace("-","");
        try{
            // 加鎖
            Boolean flag = template.opsForValue().setIfAbsent(REDIS_LOCK, value);
            // 加鎖失敗
            if(!flag){
                return "搶鎖失敗!";
            }
            System.out.println( value+ " 搶鎖成功");
            // 業(yè)務(wù)邏輯
            String result = template.opsForValue().get("001");
            int total = result == null ? 0 : Integer.parseInt(result);
            if (total > 0) {
                int realTotal = total - 1;
                template.opsForValue().set("001", String.valueOf(realTotal));
                // 如果在搶到所之后,刪除鎖之前,發(fā)生了異常,鎖就無法被釋放,
                // 釋放鎖操作不能在此操作,要在finally處理
                // template.delete(REDIS_LOCK);
                System.out.println("購買商品成功,庫存還剩:" + realTotal + "件");
                return "購買商品成功,庫存還剩:" + realTotal + "件";
            } else {
                System.out.println("購買商品失敗");
            }
            return "購買商品失敗";
        }finally {
            // 釋放鎖
            template.delete(REDIS_LOCK);
        }
    }   
}

該種實(shí)現(xiàn)方案比較簡單,但是有一些問題。假如服務(wù)運(yùn)行期間掛掉了,代碼完成了加鎖的處理,但是沒用走的finally部分,即鎖沒有釋放,這樣的情況下,鎖是永遠(yuǎn)沒法釋放的。于是就有了改進(jìn)版本。

進(jìn)階版本

/**
 * 進(jìn)階版本
 * @author:liyajie
 * @createTime:2022/6/22 15:42
 * @version:1.0
 */
public class SimplyRedisLock2 {
    // Redis分布式鎖的key
    public static final String REDIS_LOCK = "redis_lock";

    @Autowired
    StringRedisTemplate template;

    public String index(){

        // 每個(gè)人進(jìn)來先要進(jìn)行加鎖,key值為"redis_lock",value隨機(jī)生成
        String value = UUID.randomUUID().toString().replace("-","");
        try{
            // 加鎖
            Boolean flag = template.opsForValue().setIfAbsent(REDIS_LOCK, value,10L, TimeUnit.SECONDS);
            // 加鎖失敗
            if(!flag){
                return "搶鎖失??!";
            }
            System.out.println( value+ " 搶鎖成功");
            // 業(yè)務(wù)邏輯
            String result = template.opsForValue().get("001");
            int total = result == null ? 0 : Integer.parseInt(result);
            if (total > 0) {
                int realTotal = total - 1;
                template.opsForValue().set("001", String.valueOf(realTotal));
                // 如果在搶到所之后,刪除鎖之前,發(fā)生了異常,鎖就無法被釋放,
                // 釋放鎖操作不能在此操作,要在finally處理
                // template.delete(REDIS_LOCK);
                System.out.println("購買商品成功,庫存還剩:" + realTotal + "件");
                return "購買商品成功,庫存還剩:" + realTotal + "件";
            } else {
                System.out.println("購買商品失敗");
            }
            return "購買商品失敗";
        }finally {
            // 釋放鎖
            template.delete(REDIS_LOCK);
        }
    }   
}

這種實(shí)現(xiàn)方案,對key增加了一個(gè)過期時(shí)間,這樣即使服務(wù)掛掉,到了過期時(shí)間之后,鎖會自動釋放。但是仔細(xì)想想,還是有問題。比如key值的過期時(shí)間為10s,但是業(yè)務(wù)處理邏輯需要15s的時(shí)間,這樣就會導(dǎo)致某一個(gè)線程處理完業(yè)務(wù)邏輯之后,在釋放鎖,即刪除key的時(shí)候,刪除的key不是自己set的,而是其他線程設(shè)置的,這樣就會造成數(shù)據(jù)的不一致性,引起數(shù)據(jù)的錯(cuò)誤,從而影響業(yè)務(wù)。還需要改進(jìn)。

進(jìn)階版本2-誰設(shè)置的鎖,誰釋放

/**
 * 進(jìn)階版本2-誰設(shè)置的鎖,誰釋放
 * @author:liyajie
 * @createTime:2022/6/22 15:42
 * @version:1.0
 */
public class SimplyRedisLock3 {
    // Redis分布式鎖的key
    public static final String REDIS_LOCK = "redis_lock";

    @Autowired
    StringRedisTemplate template;

    public String index(){

        // 每個(gè)人進(jìn)來先要進(jìn)行加鎖,key值為"redis_lock",value隨機(jī)生成
        String value = UUID.randomUUID().toString().replace("-","");
        try{
            // 加鎖
            Boolean flag = template.opsForValue().setIfAbsent(REDIS_LOCK, value,10L, TimeUnit.SECONDS);
            // 加鎖失敗
            if(!flag){
                return "搶鎖失??!";
            }
            System.out.println( value+ " 搶鎖成功");
            // 業(yè)務(wù)邏輯
            String result = template.opsForValue().get("001");
            int total = result == null ? 0 : Integer.parseInt(result);
            if (total > 0) {
                int realTotal = total - 1;
                template.opsForValue().set("001", String.valueOf(realTotal));
                // 如果在搶到所之后,刪除鎖之前,發(fā)生了異常,鎖就無法被釋放,
                // 釋放鎖操作不能在此操作,要在finally處理
                // template.delete(REDIS_LOCK);
                System.out.println("購買商品成功,庫存還剩:" + realTotal + "件");
                return "購買商品成功,庫存還剩:" + realTotal + "件";
            } else {
                System.out.println("購買商品失敗");
            }
            return "購買商品失敗";
        }finally {
            // 誰加的鎖,誰才能刪除!?。?!
            if(template.opsForValue().get(REDIS_LOCK).equals(value)){
                template.delete(REDIS_LOCK);
            }
        }
    }   
}

這種方式解決了因業(yè)務(wù)復(fù)雜,處理時(shí)間太長,超過了過期時(shí)間,而釋放了別人鎖的問題。還會有其他問題嗎?其實(shí)還是有的,finally塊的判斷和del刪除操作不是原子操作,并發(fā)的時(shí)候也會出問題,并發(fā)就是要保證數(shù)據(jù)的一致性,保證數(shù)據(jù)的一致性,最好要保證對數(shù)據(jù)的操作具有原子性。于是還是要改進(jìn)。

進(jìn)階版本3-Lua版本

/**
 * 進(jìn)階版本-Lua版本
 * @author:liyajie
 * @createTime:2022/6/22 15:42
 * @version:1.0
 */
public class SimplyRedisLock3 {
    // Redis分布式鎖的key
    public static final String REDIS_LOCK = "redis_lock";

    @Autowired
    StringRedisTemplate template;

    public String index(){

        // 每個(gè)人進(jìn)來先要進(jìn)行加鎖,key值為"redis_lock",value隨機(jī)生成
        String value = UUID.randomUUID().toString().replace("-","");
        try{
            // 加鎖
            Boolean flag = template.opsForValue().setIfAbsent(REDIS_LOCK, value,10L, TimeUnit.SECONDS);
            // 加鎖失敗
            if(!flag){
                return "搶鎖失??!";
            }
            System.out.println( value+ " 搶鎖成功");
            // 業(yè)務(wù)邏輯
            String result = template.opsForValue().get("001");
            int total = result == null ? 0 : Integer.parseInt(result);
            if (total > 0) {
                int realTotal = total - 1;
                template.opsForValue().set("001", String.valueOf(realTotal));
                // 如果在搶到所之后,刪除鎖之前,發(fā)生了異常,鎖就無法被釋放,
                // 釋放鎖操作不能在此操作,要在finally處理
                // template.delete(REDIS_LOCK);
                System.out.println("購買商品成功,庫存還剩:" + realTotal + "件");
                return "購買商品成功,庫存還剩:" + realTotal + "件";
            } else {
                System.out.println("購買商品失敗");
            }
            return "購買商品失敗";
        }finally {
            // 誰加的鎖,誰才能刪除,使用Lua腳本,進(jìn)行鎖的刪除
            Jedis jedis = null;
            try{
                jedis = RedisUtils.getJedis();
                String script = "if redis.call('get',KEYS[1]) == ARGV[1] " +
                        "then " +
                        "return redis.call('del',KEYS[1]) " +
                        "else " +
                        "   return 0 " +
                        "end";

                Object eval = jedis.eval(script, Collections.singletonList(REDIS_LOCK), Collections.singletonList(value));
                if("1".equals(eval.toString())){
                    System.out.println("-----del redis lock ok....");
                }else{
                    System.out.println("-----del redis lock error ....");
                }
            }catch (Exception e){

            }finally {
                if(null != jedis){
                    jedis.close();
                }
            }
        }
    }   
}

這種方式,規(guī)定了誰上的鎖,誰才能刪除,并且解決了刪除操作沒有原子性問題。但還沒有考慮緩存,以及Redis集群部署下,異步復(fù)制造成的鎖丟失:主節(jié)點(diǎn)沒來得及把剛剛set進(jìn)來這條數(shù)據(jù)給從節(jié)點(diǎn),就掛了。所以還得改進(jìn)。

終極進(jìn)化版

/**
 * 終極進(jìn)化版
 * @author:liyajie
 * @createTime:2022/6/22 15:42
 * @version:1.0
 */
public class SimplyRedisLock5 {
    // Redis分布式鎖的key
    public static final String REDIS_LOCK = "redis_lock";

    @Autowired
    StringRedisTemplate template;

    @Autowired
    Redisson redisson;

    public String index(){

        RLock lock = redisson.getLock(REDIS_LOCK);
        lock.lock();
        // 每個(gè)人進(jìn)來先要進(jìn)行加鎖,key值為"redis_lock"
        String value = UUID.randomUUID().toString().replace("-","");
        try {
            String result = template.opsForValue().get("001");
            int total = result == null ? 0 : Integer.parseInt(result);
            if (total > 0) {
                // 如果在此處需要調(diào)用其他微服務(wù),處理時(shí)間較長。。。
                int realTotal = total - 1;
                template.opsForValue().set("001", String.valueOf(realTotal));
                System.out.println("購買商品成功,庫存還剩:" + realTotal + "件");
                return "購買商品成功,庫存還剩:" + realTotal + "件";
            } else {
                System.out.println("購買商品失敗");
            }
            return "購買商品失敗";
        }finally {
            if(lock.isLocked() && lock.isHeldByCurrentThread()){
                lock.unlock();
            }
        }
    }   
}

這種實(shí)現(xiàn)方案,底層封裝了多節(jié)點(diǎn)redis實(shí)現(xiàn)的分布式鎖算法,有效防止單點(diǎn)故障,感興趣的可以去研究一下。

總結(jié)

分析問題的過程,也是解決問題的過程,也能鍛煉自己編寫代碼時(shí)思考問題的方式和角度。

到此這篇關(guān)于詳解Redis分布式鎖的原理與實(shí)現(xiàn)的文章就介紹到這了,更多相關(guān)Redis分布式鎖內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Redis實(shí)現(xiàn)編碼生成規(guī)則方式

    Redis實(shí)現(xiàn)編碼生成規(guī)則方式

    在自動生成編碼時(shí)應(yīng)采用“MD+年月日+4位序列號”的規(guī)則,如“MD202310130001”,為避免使用隨機(jī)序列號導(dǎo)致的重復(fù)編碼,建議使用從0開始的自增序列號,此外,使用Redis的incrBy功能實(shí)現(xiàn)序列號自增,可以有效提高效率和降低實(shí)現(xiàn)難度
    2023-01-01
  • 5分鐘教你docker安裝啟動redis全教程(全新方式)

    5分鐘教你docker安裝啟動redis全教程(全新方式)

    今天,我來帶大家使用一種全新的方式docker來安裝redis,首先我們來了解一下什么是redis以及我們?yōu)槭裁匆褂胷edis,以及它的優(yōu)缺點(diǎn),感興趣的朋友跟隨小編一起學(xué)習(xí)下吧
    2021-05-05
  • Redis事務(wù)機(jī)制與Springboot項(xiàng)目中的使用方式

    Redis事務(wù)機(jī)制與Springboot項(xiàng)目中的使用方式

    Redis事務(wù)機(jī)制允許將多個(gè)命令打包在一起,作為一個(gè)原子操作來執(zhí)行,開啟事務(wù)使用MULTI命令,執(zhí)行事務(wù)使用EXEC命令,取消事務(wù)使用DISCARD命令,監(jiān)視一個(gè)或多個(gè)鍵使用WATCH命令,Redis事務(wù)的核心思想是將多個(gè)命令放入一個(gè)隊(duì)列中
    2025-03-03
  • Redis如何實(shí)現(xiàn)計(jì)數(shù)統(tǒng)計(jì)

    Redis如何實(shí)現(xiàn)計(jì)數(shù)統(tǒng)計(jì)

    這篇文章主要介紹了Redis如何實(shí)現(xiàn)計(jì)數(shù)統(tǒng)計(jì)方式,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2024-04-04
  • Redis過期刪除策略與內(nèi)存淘汰策略

    Redis過期刪除策略與內(nèi)存淘汰策略

    這篇文章主要介紹了Redis過期刪除策略與內(nèi)存淘汰策略,文章圍繞主題展開詳細(xì)的內(nèi)容戒殺,具有一定的參考價(jià)值,需要的小伙伴可以參考一下
    2022-09-09
  • 基于Redis實(shí)現(xiàn)分布式鎖的方法(lua腳本版)

    基于Redis實(shí)現(xiàn)分布式鎖的方法(lua腳本版)

    這篇文章主要介紹了基于Redis實(shí)現(xiàn)分布式鎖的方法(lua腳本版),本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2021-05-05
  • Redis的主從同步解析

    Redis的主從同步解析

    這篇文章主要介紹了Redis的主從同步解析,見識淺薄,僅供參考。
    2017-10-10
  • Redis的幾種數(shù)據(jù)類型使用詳解

    Redis的幾種數(shù)據(jù)類型使用詳解

    這篇文章主要介紹了Redis的幾種數(shù)據(jù)類型使用,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2025-04-04
  • Redis主從復(fù)制問題和擴(kuò)容問題的解決思路

    Redis主從復(fù)制問題和擴(kuò)容問題的解決思路

    這篇文章主要介紹了Redis主從復(fù)制問題和擴(kuò)容問題的解決思路,其中擴(kuò)容問題的解決思路來自Redis作者,需要的朋友可以參考下
    2014-06-06
  • kubernetes環(huán)境部署單節(jié)點(diǎn)redis數(shù)據(jù)庫的方法

    kubernetes環(huán)境部署單節(jié)點(diǎn)redis數(shù)據(jù)庫的方法

    這篇文章主要介紹了kubernetes環(huán)境部署單節(jié)點(diǎn)redis數(shù)據(jù)庫的方法,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2021-01-01

最新評論

故城县| 镇赉县| 都安| 天峨县| 阳新县| 军事| 兰考县| 宜良县| 镶黄旗| 大方县| 合肥市| 元谋县| 黔南| 南陵县| 通州市| 金阳县| 健康| 柳林县| 嵊泗县| 缙云县| 阜城县| 绿春县| 道孚县| 阿坝| 东兰县| 华安县| 新野县| 无棣县| 太湖县| 浮山县| 峨边| 元阳县| 汉沽区| 崇文区| 仪陇县| 长汀县| 嘉峪关市| 余庆县| 分宜县| 宾阳县| 西乌珠穆沁旗|