Redisson失效場(chǎng)景

一、失效場(chǎng)景說(shuō)明

環(huán)境是Redis集群狞谱,下面主要列舉三種場(chǎng)景,其中場(chǎng)景一和場(chǎng)景二在開(kāi)發(fā)過(guò)程中會(huì)經(jīng)常遇到禁漓。場(chǎng)景三出現(xiàn)的機(jī)率比較小跟衅,但是能加深我們對(duì)分布式鎖的理解。

二播歼、失效場(chǎng)景場(chǎng)景一(Redisson)

在事務(wù)內(nèi)部使用鎖伶跷,鎖在事務(wù)提交前釋放

2.1 場(chǎng)景描述

假設(shè)有這樣一個(gè)需求:創(chuàng)建付款單,要求不能重復(fù)創(chuàng)建相同業(yè)務(wù)單號(hào)的付款單荚恶。為了保證冪等撩穿,我們需要判斷數(shù)據(jù)庫(kù)中是否已經(jīng)存在相同業(yè)務(wù)單號(hào)的付款單磷支,并且需要加鎖處理并發(fā)安全性問(wèn)題谒撼。

@Transactional
public void createPaymentOrderInnerLock(PaymentOrder paymentOrder){
    RLock lock = redissonClient.getLock(paymentOrder.getBizNo());
    //采用的redisson可重入鎖,提供watchdog機(jī)制雾狈,在鎖釋放前默認(rèn)每10s重置鎖失效時(shí)間為30s
    lock.lock();
    try {
        LambdaQueryWrapper<PaymentOrder> paymentOrderLambdaQueryWrapper = new LambdaQueryWrapper<>();
        paymentOrderLambdaQueryWrapper.eq(PaymentOrder::getBizNo,paymentOrder.getBizNo());
        //判斷數(shù)據(jù)庫(kù)中是否存在相同業(yè)務(wù)單號(hào)的付款單
        long count = this.count(paymentOrderLambdaQueryWrapper);
        //存在相同業(yè)務(wù)單號(hào)的付款單則拋異常
        if(count>0){
            throw new RuntimeException("不可重復(fù)提交付款單");
        }else{
            //無(wú)重復(fù)數(shù)據(jù)廓潜,創(chuàng)建付款單
            this.save(paymentOrder);
            //其他DB操作
            ...
        }
    } finally {
            // 釋放鎖
            if (lock.isLocked() && lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
    }
}
2.2 問(wèn)題分析

上述問(wèn)題的流程圖如下

1
2.3 解決方案

為了避免鎖在事務(wù)提交前釋放,我們應(yīng)該在事務(wù)外層使用鎖。

  • 方式一:在Controller層用Redisson辩蛋,而不是在Service層用Redisson呻畸。
  • 方式二:在Service層用Redisson,不用聲明式事務(wù)悼院,而采用編程式事務(wù)(最小范圍控制事務(wù))伤为。

三、失效場(chǎng)景場(chǎng)景二(非Redisson)

業(yè)務(wù)未執(zhí)行完据途,鎖超時(shí)釋放

3.1 場(chǎng)景描述

需求:創(chuàng)建付款單绞愚,要求不能重復(fù)創(chuàng)建相同業(yè)務(wù)單號(hào)的付款單

@Override
public void createPaymentOrderRenault(List<PaymentOrder> paymentOrderList){
    if(!CollectionUtils.isEmpty(paymentOrderList)){
        for (PaymentOrder paymentOrder : paymentOrderList) {
            /**
             * 采用公司框架提供的分布式鎖
             * 10---等待鎖釋放時(shí)間
             * 1---嘗試獲取鎖時(shí)間間隔
             * 5---鎖失效時(shí)間
             * 注意:此處設(shè)置鎖失效時(shí)間為5秒,在createPaymentOrderNoLock中睡眠5秒模擬耗時(shí)操作颖医,此時(shí)會(huì)出現(xiàn)業(yè)務(wù)未執(zhí)行完位衩,鎖超時(shí)釋放的問(wèn)題
             */
            try (AutoReleaseLock lock = acquireLock(paymentOrder.getBizNo(),  10, 1, 5, TimeUnit.SECONDS)) {
                if(lock != null) {
                    paymentOrderService.createPaymentOrderNoLock(paymentOrder);
                } else {
                    log.info("未獲取到鎖!");
                }
            }catch (CacheParamException e) {
                log.info("獲取鎖失敗");
            }
        }
    }
}


@Override
@Transactional
public void createPaymentOrderNoLock(PaymentOrder paymentOrder) {
    LambdaQueryWrapper<PaymentOrder> paymentOrderLambdaQueryWrapper = new LambdaQueryWrapper<>();
    paymentOrderLambdaQueryWrapper.eq(PaymentOrder::getBizNo,paymentOrder.getBizNo());
    long count = this.count(paymentOrderLambdaQueryWrapper);
    if(count>0){
        log.info("不可重復(fù)提交付款單");
        throw new RuntimeException("不可重復(fù)提交付款單");
    }else{
        this.save(paymentOrder);
        //模擬耗時(shí)操作...
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
3.2 問(wèn)題分析

出現(xiàn)上述問(wèn)題是因?yàn)樵谥付ǖ逆i的失效時(shí)間內(nèi)(并且沒(méi)有續(xù)命機(jī)制)熔萧,鎖內(nèi)部的業(yè)務(wù)代碼沒(méi)有執(zhí)行完糖驴,鎖超時(shí)釋放了。尤其我們財(cái)務(wù)端處于業(yè)務(wù)鏈下游佛致,處理的數(shù)據(jù)量一般都比較大贮缕,交互的端比較多,尤其要注意這種情況俺榆。下列情形都有可能出現(xiàn)代碼沒(méi)有執(zhí)行完跷睦,鎖超時(shí)釋放的問(wèn)題。

  • 鎖的失效時(shí)間設(shè)置的太短
  • 鎖的粒度太大肋演,處理鏈路冗長(zhǎng)
  • 鎖內(nèi)部包含很多耗時(shí)操作抑诸,比如遠(yuǎn)程調(diào)用、大數(shù)據(jù)量處理等
3.3 解決方案

首先會(huì)想到爹殊,把失效時(shí)間設(shè)置長(zhǎng)一點(diǎn)蜕乡,確實(shí)可以。但設(shè)置多長(zhǎng)合適呢梗夸,設(shè)置過(guò)長(zhǎng)有可能存在拿到鎖的客戶端宕掉了层玲,此時(shí)就要等鎖過(guò)期才能釋放,其他節(jié)點(diǎn)處于阻塞狀態(tài)反症,降低了系統(tǒng)吞吐辛块。又或者預(yù)估了一個(gè)失效時(shí)間在項(xiàng)目初期沒(méi)問(wèn)題,隨著數(shù)據(jù)量增多铅碍,或者其他一些不確定因素造成了超時(shí)润绵,也會(huì)出現(xiàn)問(wèn)題。

可以采用類(lèi)似Redisson的watchdog機(jī)制給鎖續(xù)命胞谈。另外尘盼,注意減小鎖的粒度憨愉,把存在并發(fā)安全性問(wèn)題的關(guān)鍵代碼鎖住即可,增加系統(tǒng)吞吐量卿捎。同時(shí)也要注意減小事務(wù)的粒度配紫,把查詢操作、甚至一些遠(yuǎn)程調(diào)用放到事務(wù)外部(注意讀寫(xiě)分離的情況)午阵,避免出現(xiàn)大事務(wù)問(wèn)題躺孝。

四、失效場(chǎng)景場(chǎng)景三(非Redisson)

Redis節(jié)點(diǎn)主從切換

4.1 場(chǎng)景描述

我們?cè)谑褂肦edis時(shí)底桂,一般會(huì)采用主從集群 + 哨兵的模式部署括细,這樣做的好處在于當(dāng)主庫(kù)異常宕機(jī)時(shí),哨兵可以實(shí)現(xiàn)故障自動(dòng)切換戚啥,把從庫(kù)提升為主庫(kù)奋单,繼續(xù)提供服務(wù),以此保證可用性猫十。
當(dāng)【主從發(fā)生切換】時(shí)览濒,Redis分布鎖會(huì)存在安全性問(wèn)題

  • 客戶端A從master獲取到鎖

  • 在master將鎖同步到slave之前,master宕掉了拖云。

  • slave節(jié)點(diǎn)被晉升為master節(jié)點(diǎn)

  • 客戶端B取得了同一個(gè)資源被客戶端A已經(jīng)獲取到的同一個(gè)鎖贷笛。

4.2 問(wèn)題分析

首先要說(shuō)明一點(diǎn),出現(xiàn)這種情形的概率是很低的宙项。針對(duì)于這種情況乏苦,Redis的作者antirez設(shè)計(jì)出了RedLock算法,然而RedLock算法依賴時(shí)鐘正確性尤筐,存在爭(zhēng)議汇荐。

Redlock 必須「強(qiáng)依賴」多個(gè)節(jié)點(diǎn)的時(shí)鐘是保持同步的,一旦有節(jié)點(diǎn)時(shí)鐘發(fā)生錯(cuò)誤盆繁,那這個(gè)算法模型就失效了掀淘。

  • 客戶端 A 獲取節(jié)點(diǎn) 1、2油昂、3 上的鎖革娄。由于網(wǎng)絡(luò)問(wèn)題,無(wú)法訪問(wèn) 4 和 5冕碟。
  • 節(jié)點(diǎn) 3 上的時(shí)鐘向前跳躍拦惋,導(dǎo)致鎖到期。
  • 客戶端 B 獲取節(jié)點(diǎn) 3安寺、4厕妖、5 上的鎖。由于網(wǎng)絡(luò)問(wèn)題我衬,無(wú)法訪問(wèn) 1 和 2叹放。
  • 客戶端 A 和 B 現(xiàn)在都相信他們持有鎖饰恕。
4.3 Redisson棄用RedLock

起初Redisson也提供的RedLock的實(shí)現(xiàn)挠羔,但在3.12.5版本后棄用了井仰。

//redisson 3.12.5版本之前 RedLock 使用示例,基于RedissonMultiLock實(shí)現(xiàn)
RLock lock1 = redissonInstance1.getLock("lock1");
RLock lock2 = redissonInstance2.getLock("lock2");
RLock lock3 = redissonInstance3.getLock("lock3");

RedissonRedLock lock = new RedissonRedLock(lock1, lock2, lock3);
// 同時(shí)加鎖:lock1 lock2 lock3
// 紅鎖在大部分節(jié)點(diǎn)上加鎖成功就算成功破加。
lock.lock();
...
lock.unlock();

Redisson 的開(kāi)發(fā)者認(rèn)為 Redis 的紅鎖存在爭(zhēng)議俱恶,但是為了保證可用性,RLock 對(duì)象執(zhí)行的每個(gè) Redis 命令執(zhí)行都通過(guò) Redis 3.0 中引入的 WAIT 命令進(jìn)行同步范舀。WAIT 命令會(huì)阻塞當(dāng)前客戶端合是,直到所有以前的寫(xiě)命令都成功的傳輸并被指定數(shù)量的副本確認(rèn)。如果達(dá)到以毫秒為單位指定的超時(shí)锭环,則即使尚未達(dá)到指定數(shù)量的副本聪全,該命令也會(huì)返回。WAIT 命令同步復(fù)制也并不能保證強(qiáng)一致性辅辩,不過(guò)在主節(jié)點(diǎn)宕機(jī)之后难礼,只不過(guò)會(huì)盡可能的選擇最佳的副本(slaves)。

4.4 解決方案

Redis分布式鎖在極端情況下玫锋,不一定是安全的蛾茉。如果你對(duì)并發(fā)安全性帶來(lái)的問(wèn)題零容忍,為了保證正確性撩鹿,我們可以做一些兜底工作谦炬,
例如:

  • 建立唯一索引
  • 監(jiān)控、告警节沦、提供補(bǔ)償方案

轉(zhuǎn)載自:Redis分布式鎖失效場(chǎng)景分析

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末键思,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子甫贯,更是在濱河造成了極大的恐慌稚机,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,729評(píng)論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件获搏,死亡現(xiàn)場(chǎng)離奇詭異赖条,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)常熙,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,226評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門(mén)纬乍,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人裸卫,你說(shuō)我怎么就攤上這事仿贬。” “怎么了墓贿?”我有些...
    開(kāi)封第一講書(shū)人閱讀 169,461評(píng)論 0 362
  • 文/不壞的土叔 我叫張陵茧泪,是天一觀的道長(zhǎng)蜓氨。 經(jīng)常有香客問(wèn)我,道長(zhǎng)队伟,這世上最難降的妖魔是什么穴吹? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 60,135評(píng)論 1 300
  • 正文 為了忘掉前任,我火速辦了婚禮嗜侮,結(jié)果婚禮上港令,老公的妹妹穿的比我還像新娘。我一直安慰自己锈颗,他們只是感情好顷霹,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,130評(píng)論 6 398
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著击吱,像睡著了一般淋淀。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上覆醇,一...
    開(kāi)封第一講書(shū)人閱讀 52,736評(píng)論 1 312
  • 那天朵纷,我揣著相機(jī)與錄音,去河邊找鬼叫乌。 笑死柴罐,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的憨奸。 我是一名探鬼主播革屠,決...
    沈念sama閱讀 41,179評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼排宰!你這毒婦竟也來(lái)了似芝?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 40,124評(píng)論 0 277
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤板甘,失蹤者是張志新(化名)和其女友劉穎党瓮,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體盐类,經(jīng)...
    沈念sama閱讀 46,657評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡寞奸,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,723評(píng)論 3 342
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了在跳。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片枪萄。...
    茶點(diǎn)故事閱讀 40,872評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖猫妙,靈堂內(nèi)的尸體忽然破棺而出瓷翻,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 36,533評(píng)論 5 351
  • 正文 年R本政府宣布齐帚,位于F島的核電站妒牙,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏对妄。R本人自食惡果不足惜湘今,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,213評(píng)論 3 336
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望饥伊。 院中可真熱鬧象浑,春花似錦蔫饰、人聲如沸琅豆。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,700評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)茫因。三九已至,卻和暖如春杖剪,著一層夾襖步出監(jiān)牢的瞬間冻押,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,819評(píng)論 1 274
  • 我被黑心中介騙來(lái)泰國(guó)打工盛嘿, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留洛巢,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 49,304評(píng)論 3 379
  • 正文 我出身青樓次兆,卻偏偏與公主長(zhǎng)得像稿茉,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子芥炭,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,876評(píng)論 2 361

推薦閱讀更多精彩內(nèi)容