面試官喜歡這樣問:Redis分布式鎖免姿,如何實現(xiàn)饼酿?(問題分析)

一、分布式鎖簡介

1.什么是分布式鎖

  • 當(dāng)在分布式模型下,數(shù)據(jù)只有一份(或有限制)故俐,此時需要利用鎖的技術(shù)控制某一時刻修改數(shù)據(jù)的進程數(shù)想鹰。
  • 與單機模式下的鎖不僅需要保證進程可見,還需要考慮進程與鎖之間的網(wǎng)絡(luò)問題药版。
  • 分布式鎖還是可以將標(biāo)記存在內(nèi)存辑舷,只是該內(nèi)存不是某個進程分配的內(nèi)存而是公共內(nèi)存如 Redis、Memcache槽片。至于利用數(shù)據(jù)庫何缓、文件等做鎖與單機的實現(xiàn)是一樣的,只要保證標(biāo)記能互斥就行还栓。

2.分布式鎖具備的條件

  • 在分布式系統(tǒng)環(huán)境下碌廓,一個方法在同一時間只能被一個機器的一個線程執(zhí)行;
  • 高可用的獲取鎖與釋放鎖剩盒;
  • 高性能的獲取鎖與釋放鎖谷婆;
  • 具備可重入特性;
  • 具備鎖失效機制勃刨,防止死鎖波材;
  • 具備非阻塞鎖特性股淡,即沒有獲取到鎖將直接返回獲取鎖失敗身隐。

二、采用Redis實現(xiàn)分布式鎖

1.常規(guī)代碼實現(xiàn)

@RequestMapping("/deduct_stock")
public String deductStock() {
    String lockKey = "product_001";
    try {
       /*Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, "aaa"); //jedis.setnx
        stringRedisTemplate.expire(lockKey, 30, TimeUnit.SECONDS); //設(shè)置超時*/
        //為解決原子性問題將設(shè)置鎖和設(shè)置超時時間合并
        Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, "aaa", 10, TimeUnit.SECONDS);

        //未設(shè)置成功唯灵,當(dāng)前key已經(jīng)存在了贾铝,直接返回錯誤
        if (!result) {
            return "error_code";
        }

        //業(yè)務(wù)邏輯實現(xiàn),扣減庫存
        ....
    } catch (Exception e) {
        e.printStackTrace();
    }finally {
        stringRedisTemplate.delete(lockKey);
    }
    return "end";
}

2.問題分析

上述代碼可以看到埠帕,當(dāng)前鎖的失效時間為10s垢揩,如果當(dāng)前扣減庫存的業(yè)務(wù)邏輯執(zhí)行需要15s時,高并發(fā)時會出現(xiàn)問題:

  • 線程1敛瓷,首先執(zhí)行到10s后叁巨,鎖(product_001)失效
  • 線程2,在第10s后同樣進入當(dāng)前方法呐籽,此時加上鎖(product_001)
  • 當(dāng)執(zhí)行到15s時锋勺,線程1刪除線程2加的鎖(product_001)
  • 線程3,可以加鎖 .... 如此循環(huán)狡蝶,實際鎖已經(jīng)沒有意義

a)方案1:當(dāng)前線程刪除當(dāng)前線程所加的鎖

@RequestMapping("/deduct_stock")
public String deductStock() {
    String lockKey = "product_001";
    //定義唯一的客戶端ID
    String clientId = UUID.randomUUID().toString();
    try {
        //為解決原子性問題將設(shè)置鎖和設(shè)置超時時間合并,將clientID作為值放入鎖中
        Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, clientId, 10, TimeUnit.SECONDS);

        //未設(shè)置成功庶橱,當(dāng)前key已經(jīng)存在了,直接返回錯誤
        if (!result) {
            return "error_code";
        }

        //業(yè)務(wù)邏輯實現(xiàn)贪惹,扣減庫存
        ....
    } catch (Exception e) {
        e.printStackTrace();
    }finally {
        //只有在獲取鎖的值為當(dāng)前clientId時才會進行刪除鎖操作
        if (clientId.equals(stringRedisTemplate.opsForValue().get(lockKey))) {
            stringRedisTemplate.delete(lockKey);
        }
    }
    return "end";
}

這樣能保證每個線程刪除的鎖為當(dāng)前線程添加的鎖苏章,但是 還是會有超賣的問題 :因為 線程1在還沒有執(zhí)行完成的時候,此時鎖已經(jīng)到達過期時間,此時線程2則會加鎖成功

b)方案2:續(xù)命鎖

定義一個子線程枫绅,定時去查看 是否存在主線程的持有當(dāng)前鎖 泉孩,如果 存在則為其延長過期時間

c)方案3:Redisson

@Autowired
Redisson redisson;
@RequestMapping("/deduct_stock_redisson")
public String deductStockRedisson() {
    String lockKey = "product_001";
    RLock rlock = redisson.getLock(lockKey);
    try {
        rlock.lock();

        //業(yè)務(wù)邏輯實現(xiàn)并淋,扣減庫存
        ....
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        rlock.unlock();
    }
    return "end";
}
面試官喜歡這樣問:Redis分布式鎖棵譬,如何實現(xiàn)?(問題分析)
  • 多個線程去執(zhí)行l(wèi)ock操作预伺,僅有一個線程能夠加鎖成功订咸,其它線程循環(huán)阻塞。
  • 加鎖成功酬诀,鎖超時時間 默認30s 脏嚷,并開啟后臺線程,加鎖的后臺會 每隔10秒 去檢測線程持有的鎖是否存在瞒御,還在的話父叙,就延遲鎖超時時間,重新設(shè)置為30s肴裙,即 鎖延期趾唱。
  • 對于原子性,Redis分布式鎖底層借助 Lua腳本實現(xiàn)鎖的原子性 蜻懦。鎖延期是通過在底層用Lua進行延時甜癞,延時檢測時間是對超時時間timeout /3

三宛乃、采用Redisson分布式鎖的問題分析

1.主從同步問題

當(dāng)主Redis加鎖了悠咱,開始執(zhí)行線程,若還未將鎖通過異步同步的方式同步到從Redis節(jié)點,主節(jié)點就掛了谆奥,此時會把某一臺從節(jié)點作為新的主節(jié)點眼坏,此時別的線程就可以加鎖了,這樣就出錯了酸些,怎么辦宰译?

a)采用zookeeper代替Redis

由于zk集群的特點,其支持的是CP擂仍。而Redis集群支持的則是AP囤屹。

b)采用RedLock

面試官喜歡這樣問:Redis分布式鎖,如何實現(xiàn)逢渔?(問題分析)

假設(shè)有3個redis節(jié)點肋坚,這些節(jié)點之間既沒有主從,也沒有集群關(guān)系≈茄幔客戶端用相同的key和隨機值在3個節(jié)點上請求鎖诲泌,請求鎖的超時時間應(yīng)小于鎖自動釋放時間。當(dāng)在2個(超過半數(shù))redis上請求到鎖的時候铣鹏,才算是真正獲取到了鎖敷扫。如果沒有獲取到鎖,則把部分已鎖的redis釋放掉诚卸。

@RequestMapping("/deduct_stock_redlock")
public String deductStockRedlock() {
    String lockKey = "product_001";
    //TODO 這里需要自己實例化不同redis實例的redisson客戶端連接葵第,這里只是偽代碼用一個redisson客戶端簡化了
    RLock rLock1 = redisson.getLock(lockKey);
    RLock rLock2 = redisson.getLock(lockKey);
    RLock rLock3 = redisson.getLock(lockKey);

    // 向3個redis實例嘗試加鎖
    RedissonRedLock redLock = new RedissonRedLock(rLock1, rLock2, rLock3);
    boolean isLock;
    try {
        // 500ms拿不到鎖, 就認為獲取鎖失敗。10000ms即10s是鎖失效時間合溺。
        isLock = redLock.tryLock(500, 10000, TimeUnit.MILLISECONDS);
        System.out.println("isLock = " + isLock);
        if (isLock) {
            //業(yè)務(wù)邏輯處理
            ...
        }
    } catch (Exception e) {

    } finally {
        // 無論如何, 最后都要解鎖
        redLock.unlock();
    }
}

具體使用存在爭議卒密,不太推薦使用。 如果考慮高可用并發(fā)推薦使用Redisson棠赛,考慮一致性推薦使用zookeeper 哮奇。

2.提高并發(fā):分段鎖

由于Redisson實際上就是將并行的請求,轉(zhuǎn)化為串行請求睛约。這樣就降低了并發(fā)的響應(yīng)速度鼎俘,為了解決這一問題,可以將鎖進行分段處理:例如秒殺商品001辩涝,原本存在1000個商品贸伐,可以將其分為20段,為每段分配50個商品...

以上就是有關(guān)Redis分布式鎖的學(xué)習(xí)筆記膀值,希望可以對大家學(xué)習(xí)Redis分布式鎖有幫助棍丐,喜歡的小伙伴可以幫忙轉(zhuǎn)發(fā)+關(guān)注,感謝大家沧踏!LZ也會不定時的分享干貨!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末巾钉,一起剝皮案震驚了整個濱河市翘狱,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌砰苍,老刑警劉巖潦匈,帶你破解...
    沈念sama閱讀 216,372評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異赚导,居然都是意外死亡茬缩,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評論 3 392
  • 文/潘曉璐 我一進店門吼旧,熙熙樓的掌柜王于貴愁眉苦臉地迎上來凰锡,“玉大人,你說我怎么就攤上這事〉辔” “怎么了裕膀?”我有些...
    開封第一講書人閱讀 162,415評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長勇哗。 經(jīng)常有香客問我昼扛,道長,這世上最難降的妖魔是什么欲诺? 我笑而不...
    開封第一講書人閱讀 58,157評論 1 292
  • 正文 為了忘掉前任抄谐,我火速辦了婚禮,結(jié)果婚禮上扰法,老公的妹妹穿的比我還像新娘斯稳。我一直安慰自己,他們只是感情好迹恐,可當(dāng)我...
    茶點故事閱讀 67,171評論 6 388
  • 文/花漫 我一把揭開白布挣惰。 她就那樣靜靜地躺著,像睡著了一般殴边。 火紅的嫁衣襯著肌膚如雪憎茂。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,125評論 1 297
  • 那天锤岸,我揣著相機與錄音竖幔,去河邊找鬼。 笑死是偷,一個胖子當(dāng)著我的面吹牛拳氢,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播蛋铆,決...
    沈念sama閱讀 40,028評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼馋评,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了刺啦?” 一聲冷哼從身側(cè)響起留特,我...
    開封第一講書人閱讀 38,887評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎玛瘸,沒想到半個月后蜕青,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,310評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡糊渊,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,533評論 2 332
  • 正文 我和宋清朗相戀三年右核,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片渺绒。...
    茶點故事閱讀 39,690評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡贺喝,死狀恐怖菱鸥,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情搜变,我是刑警寧澤采缚,帶...
    沈念sama閱讀 35,411評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站挠他,受9級特大地震影響扳抽,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜殖侵,卻給世界環(huán)境...
    茶點故事閱讀 41,004評論 3 325
  • 文/蒙蒙 一贸呢、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧拢军,春花似錦楞陷、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至度陆,卻和暖如春艾凯,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背懂傀。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評論 1 268
  • 我被黑心中介騙來泰國打工趾诗, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人蹬蚁。 一個月前我還...
    沈念sama閱讀 47,693評論 2 368
  • 正文 我出身青樓恃泪,卻偏偏與公主長得像,于是被迫代替她去往敵國和親犀斋。 傳聞我的和親對象是個殘疾皇子贝乎,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,577評論 2 353

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