1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        《我想進(jìn)大廠》之分布式鎖奪命連環(huán)9問(wèn) | 大理版人在囧途

        共 4702字,需瀏覽 10分鐘

         ·

        2021-03-10 13:15

        開(kāi)個(gè)頭,這是篇技術(shù)文章,但是昨天一天太惡心了,忍不住還是簡(jiǎn)單說(shuō)下昨天的事情。

        昨天早上11點(diǎn)飛大理,結(jié)果9點(diǎn)鐘要出門(mén)的時(shí)候發(fā)現(xiàn)密碼鎖壞了,不用密碼都能打開(kāi),一邊司機(jī)師傅在催著走,一邊連忙打電話給房東和客服找人維修,這是第一。

        然后飛機(jī)晚點(diǎn),11點(diǎn)20飛到4點(diǎn)鐘才要落地,下降的過(guò)程那叫一個(gè)顛簸,我以為都要沒(méi)了,這也是第一次暈飛機(jī),簡(jiǎn)直快吐了,這是第二。

        然后快4點(diǎn)了,飛機(jī)總算快要降落了,輪子都快著地了,結(jié)果愣是拔起來(lái)又起飛了,最后知道是大理8級(jí)大風(fēng),機(jī)長(zhǎng)不敢落地。。。這是第三。

        最后通知起飛不知道什么時(shí)候,要等大理那邊通知,沒(méi)有辦法,我們只好下飛機(jī)轉(zhuǎn)高鐵,急急忙忙的一路轉(zhuǎn),總算趕上了最后7點(diǎn)前的高鐵,否則就要等到9點(diǎn)以后了,最后一路周轉(zhuǎn),9點(diǎn)多總算到了酒店,好在酒店還算行,沒(méi)有讓我太過(guò)于失望。

        這一天搞下來(lái),整個(gè)一人在囧途,太累了。好吧,廢話就這么多,文章開(kāi)始。

        說(shuō)說(shuō)分布式鎖吧?

        對(duì)于一個(gè)單機(jī)的系統(tǒng),我們可以通過(guò)synchronized或者ReentrantLock等這些常規(guī)的加鎖方式來(lái)實(shí)現(xiàn),然而對(duì)于一個(gè)分布式集群的系統(tǒng)而言,單純的本地鎖已經(jīng)無(wú)法解決問(wèn)題,所以就需要用到分布式鎖了,通常我們都會(huì)引入三方組件或者服務(wù)來(lái)解決這個(gè)問(wèn)題,比如數(shù)據(jù)庫(kù)、Redis、Zookeeper等。

        通常來(lái)說(shuō),分布式鎖要保證互斥性、不死鎖、可重入等特點(diǎn)。

        互斥性指的是對(duì)于同一個(gè)資源,任意時(shí)刻,都只有一個(gè)客戶端能持有鎖。

        不死鎖指的是必須要有鎖超時(shí)這種機(jī)制,保證在出現(xiàn)問(wèn)題的時(shí)候釋放鎖,不會(huì)出現(xiàn)死鎖的問(wèn)題。

        可重入指的是對(duì)于同一個(gè)線程,可以多次重復(fù)加鎖。

        那你分別說(shuō)說(shuō)使用數(shù)據(jù)庫(kù)、Redis和Zookeeper的實(shí)現(xiàn)原理?

        數(shù)據(jù)庫(kù)的話可以使用樂(lè)觀鎖或者悲觀鎖的實(shí)現(xiàn)方式。

        樂(lè)觀鎖通常就是數(shù)據(jù)庫(kù)中我們會(huì)有一個(gè)版本號(hào),更新數(shù)據(jù)的時(shí)候通過(guò)版本號(hào)來(lái)更新,這樣的話效率會(huì)比較高,悲觀鎖則是通過(guò)for update的方式,但是會(huì)帶來(lái)很多問(wèn)題,因?yàn)樗且粋€(gè)行級(jí)鎖,高并發(fā)的情況下可能會(huì)導(dǎo)致死鎖、客戶端連接超時(shí)等問(wèn)題,一般不推薦使用這種方式。

        Redis是通過(guò)set命令來(lái)實(shí)現(xiàn),在2.6.2版本之前,實(shí)現(xiàn)方式可能是這樣:

        setNX命令代表當(dāng)key不存在時(shí)返回成功,否則返回失敗。

        但是這種實(shí)現(xiàn)方式把加鎖和設(shè)置過(guò)期時(shí)間的步驟分成兩步,他們并不是原子操作,如果加鎖成功之后程序崩潰、服務(wù)宕機(jī)等異常情況,導(dǎo)致沒(méi)有設(shè)置過(guò)期時(shí)間,那么就會(huì)導(dǎo)致死鎖的問(wèn)題,其他線程永遠(yuǎn)都無(wú)法獲取這個(gè)鎖。

        之后的版本中,Redis提供了原生的set命令,相當(dāng)于兩命令合二為一,不存在原子性的問(wèn)題,當(dāng)然也可以通過(guò)lua腳本來(lái)解決。

        set命令如下格式:

        key 為分布式鎖的key

        value 為分布式鎖的值,一般為不同的客戶端設(shè)置不同的值

        NX 代表如果要設(shè)置的key已存在,則取消設(shè)置

        EX 代表過(guò)期時(shí)間為秒,PX則為毫秒,比如上面示例中為10秒過(guò)期

        Zookeeper是通過(guò)創(chuàng)建臨時(shí)順序節(jié)點(diǎn)的方式來(lái)實(shí)現(xiàn)。

        1. 當(dāng)需要對(duì)資源進(jìn)行加鎖時(shí),實(shí)際上就是在父節(jié)點(diǎn)之下創(chuàng)建一個(gè)臨時(shí)順序節(jié)點(diǎn)。
        2. 客戶端A來(lái)對(duì)資源加鎖,首先判斷當(dāng)前創(chuàng)建的節(jié)點(diǎn)是否為最小節(jié)點(diǎn),如果是,那么加鎖成功,后續(xù)加鎖線程阻塞等待
        3. 此時(shí),客戶端B也來(lái)嘗試加鎖,由于客戶端A已經(jīng)加鎖成功,所以客戶端B發(fā)現(xiàn)自己的節(jié)點(diǎn)并不是最小節(jié)點(diǎn),就會(huì)去取到上一個(gè)節(jié)點(diǎn),并且對(duì)上一節(jié)點(diǎn)注冊(cè)監(jiān)聽(tīng)
        4. 當(dāng)客戶端A操作完成,釋放鎖的操作就是刪除這個(gè)節(jié)點(diǎn),這樣就可以觸發(fā)監(jiān)聽(tīng)事件,客戶端B就會(huì)得到通知,同樣,客戶端B判斷自己是否為最小節(jié)點(diǎn),如果是,那么則加鎖成功

        你說(shuō)改為set命令之后就解決了問(wèn)題?那么還會(huì)不會(huì)有其他的問(wèn)題呢?

        雖然set解決了原子性的問(wèn)題,但是還是會(huì)存在兩個(gè)問(wèn)題。

        鎖超時(shí)問(wèn)題

        比如客戶端A加鎖同時(shí)設(shè)置超時(shí)時(shí)間是3秒,結(jié)果3s之后程序邏輯還沒(méi)有執(zhí)行完成,鎖已經(jīng)釋放??蛻舳薆此時(shí)也來(lái)嘗試加鎖,那么客戶端B也會(huì)加鎖成功。

        這樣的話,就導(dǎo)致了并發(fā)的問(wèn)題,如果代碼冪等性沒(méi)有處理好,就會(huì)導(dǎo)致問(wèn)題產(chǎn)生。

        鎖誤刪除

        還是類(lèi)似的問(wèn)題,客戶端A加鎖同時(shí)設(shè)置超時(shí)時(shí)間3秒,結(jié)果3s之后程序邏輯還沒(méi)有執(zhí)行完成,鎖已經(jīng)釋放??蛻舳薆此時(shí)也來(lái)嘗試加鎖,這時(shí)客戶端A代碼執(zhí)行完成,執(zhí)行釋放鎖,結(jié)果釋放了客戶端B的鎖。

        那上面兩個(gè)問(wèn)題你有什么好的解決方案嗎?

        鎖超時(shí)

        這個(gè)有兩個(gè)解決方案。

        1. 針對(duì)鎖超時(shí)的問(wèn)題,我們可以根據(jù)平時(shí)業(yè)務(wù)執(zhí)行時(shí)間做大致的評(píng)估,然后根據(jù)評(píng)估的時(shí)間設(shè)置一個(gè)較為合理的超時(shí)時(shí)間,這樣能一大部分程度上避免問(wèn)題。
        2. 自動(dòng)續(xù)租,通過(guò)其他的線程為將要過(guò)期的鎖延長(zhǎng)持有時(shí)間

        鎖誤刪除

        每個(gè)客戶端的鎖只能自己解鎖,一般我們可以在使用set命令的時(shí)候生成隨機(jī)的value,解鎖使用lua腳本判斷當(dāng)前鎖是否自己持有的,是自己的鎖才能釋放。

        #加鎖
        SET key random_value NX EX 10
        #解鎖
        if redis.call("get",KEYS[1]) == ARGV[1] then
            return redis.call("del",KEYS[1])
        else
            return 0
        end

        了解RedLock算法嗎?

        因?yàn)樵赗edis的主從架構(gòu)下,主從同步是異步的,如果在Master節(jié)點(diǎn)加鎖成功后,指令還沒(méi)有同步到Slave節(jié)點(diǎn),此時(shí)Master掛掉,Slave被提升為Master,新的Master上并沒(méi)有鎖的數(shù)據(jù),其他的客戶端仍然可以加鎖成功。

        對(duì)于這種問(wèn)題,Redis作者提出了RedLock紅鎖的概念。

        RedLock的理念下需要至少2個(gè)Master節(jié)點(diǎn),多個(gè)Master節(jié)點(diǎn)之間完全互相獨(dú)立,彼此之間不存在主從同步和數(shù)據(jù)復(fù)制。

        主要步驟如下:

        1. 獲取當(dāng)前Unix時(shí)間
        2. 按照順序依次嘗試從多個(gè)節(jié)點(diǎn)鎖,如果獲取鎖的時(shí)間小于超時(shí)時(shí)間,并且超過(guò)半數(shù)的節(jié)點(diǎn)獲取成功,那么加鎖成功。這樣做的目的就是為了避免某些節(jié)點(diǎn)已經(jīng)宕機(jī)的情況下,客戶端還在一直等待響應(yīng)結(jié)果。舉個(gè)例子,假設(shè)現(xiàn)在有5個(gè)節(jié)點(diǎn),過(guò)期時(shí)間=100ms,第一個(gè)節(jié)點(diǎn)獲取鎖花費(fèi)10ms,第二個(gè)節(jié)點(diǎn)花費(fèi)20ms,第三個(gè)節(jié)點(diǎn)花費(fèi)30ms,那么最后鎖的過(guò)期時(shí)間就是100-(10+20+30),這樣就是加鎖成功,反之如果最后時(shí)間<0,那么加鎖失敗
        3. 如果加鎖失敗,那么要釋放所有節(jié)點(diǎn)上的鎖

        那么RedLock有什么問(wèn)題嗎?

        其實(shí)RedLock存在不少問(wèn)題,所以現(xiàn)在其實(shí)一般不推薦使用這種方式,而是推薦使用Redission的方案,他的問(wèn)題主要如下幾點(diǎn)。

        性能、資源

        因?yàn)樾枰獙?duì)多個(gè)節(jié)點(diǎn)分別加鎖和解鎖,而一般分布式鎖的應(yīng)用場(chǎng)景都是在高并發(fā)的情況下,所以耗時(shí)較長(zhǎng),對(duì)性能有一定的影響。此外因?yàn)樾枰鄠€(gè)節(jié)點(diǎn),使用的資源也比較多,簡(jiǎn)單來(lái)說(shuō)就是費(fèi)錢(qián)。

        節(jié)點(diǎn)崩潰重啟

        比如有1~5號(hào)五個(gè)節(jié)點(diǎn),并且沒(méi)有開(kāi)啟持久化,客戶端A在1,2,3號(hào)節(jié)點(diǎn)加鎖成功,此時(shí)3號(hào)節(jié)點(diǎn)崩潰宕機(jī)后發(fā)生重啟,就丟失了加鎖信息,客戶端B在3,4,5號(hào)節(jié)點(diǎn)加鎖成功。

        那么,兩個(gè)客戶端A\B同時(shí)獲取到了同一個(gè)鎖,問(wèn)題產(chǎn)生了,怎么解決?

        1. Redis作者建議的方式就是延時(shí)重啟,比如3號(hào)節(jié)點(diǎn)宕機(jī)之后不要立刻重啟,而是等待一段時(shí)間后再重啟,這個(gè)時(shí)間必須大于鎖的有效時(shí)間,也就是鎖失效后再重啟,這種人為干預(yù)的措施真正實(shí)施起來(lái)就比較困難了
        2. 第二個(gè)方案那么就是開(kāi)啟持久化,但是這樣對(duì)性能又造成了影響。比如如果開(kāi)啟AOF默認(rèn)每秒一次刷盤(pán),那么最多丟失一秒的數(shù)據(jù),如果想完全不丟失的話就對(duì)性能造成較大的影響。

        GC、網(wǎng)絡(luò)延遲

        對(duì)于RedLock,Martin Kleppmann提出了很多質(zhì)疑,我就只舉這樣一個(gè)GC或者網(wǎng)絡(luò)導(dǎo)致的例子。(這個(gè)問(wèn)題比較多,我就不一一舉例了,心里有一個(gè)概念就行了,文章地址:https://martin.kleppmann.com/2016/02/08/how-to-do-distributed-locking.html

        從圖中我們可以看出,client1線獲取到鎖,然后發(fā)生GC停頓,超過(guò)了鎖的有效時(shí)間導(dǎo)致鎖被釋放,然后鎖被client2拿到,然后兩個(gè)客戶端同時(shí)拿到鎖在寫(xiě)數(shù)據(jù),問(wèn)題產(chǎn)生。

        圖片來(lái)自Martin Kleppmann

        時(shí)鐘跳躍

        同樣的例子,假設(shè)發(fā)生網(wǎng)絡(luò)分區(qū),4、5號(hào)節(jié)點(diǎn)變?yōu)橐粋€(gè)獨(dú)立的子網(wǎng),3號(hào)節(jié)點(diǎn)發(fā)生始終跳躍(不管人為操作還是同步導(dǎo)致)導(dǎo)致鎖過(guò)期,這時(shí)候另外的客戶端就可以從3、4、5號(hào)節(jié)點(diǎn)加鎖成功,問(wèn)題又發(fā)生了。

        那你說(shuō)說(shuō)有什么好的解決方案嗎?

        上面也提到了,其實(shí)比較好的方式是使用Redission,它是一個(gè)開(kāi)源的Java版本的Redis客戶端,無(wú)論單機(jī)、哨兵、集群環(huán)境都能支持,另外還很好地解決了鎖超時(shí)、公平非公平鎖、可重入等問(wèn)題,也實(shí)現(xiàn)了RedLock,同時(shí)也是官方推薦的客戶端版本。

        那么Redission實(shí)現(xiàn)原理呢?

        加鎖、可重入

        首先,加鎖和解鎖都是通過(guò)lua腳本去實(shí)現(xiàn)的,這樣做的好處是為了兼容老版本的redis同時(shí)保證原子性。

        KEYS[1]為鎖的key,ARGV[2]為鎖的value,格式為uuid+線程ID,ARGV[1]為過(guò)期時(shí)間。

        主要的加鎖邏輯也比較容易看懂,如果key不存在,通過(guò)hash的方式保存,同時(shí)設(shè)置過(guò)期時(shí)間,反之如果存在就是+1。

        對(duì)應(yīng)的就是hincrby', KEYS[1], ARGV[2], 1這段命令,對(duì)hash結(jié)構(gòu)的鎖重入次數(shù)+1。

        解鎖

        1. 如果key都不存在了,那么就直接返回
        2. 如果key、field不匹配,那么說(shuō)明不是自己的鎖,不能釋放,返回空
        3. 釋放鎖,重入次數(shù)-1,如果還大于0那么久刷新過(guò)期時(shí)間,反之那么久刪除鎖

        watchdog

        也叫做看門(mén)狗,也就是解決了鎖超時(shí)導(dǎo)致的問(wèn)題,實(shí)際上就是一個(gè)后臺(tái)線程,默認(rèn)每隔10秒自動(dòng)延長(zhǎng)鎖的過(guò)期時(shí)間。

        默認(rèn)的時(shí)間就是internalLockLeaseTime / 3internalLockLeaseTime默認(rèn)為30秒。

        最后,實(shí)際生產(chǎn)中對(duì)于不同的場(chǎng)景該如何選擇?

        首先,如果對(duì)于并發(fā)不高并且比較簡(jiǎn)單的場(chǎng)景,通過(guò)數(shù)據(jù)庫(kù)樂(lè)觀鎖或者唯一主鍵的形式就能解決大部分的問(wèn)題。

        然后,對(duì)于Redis實(shí)現(xiàn)的分布式鎖來(lái)說(shuō)性能高,自己去實(shí)現(xiàn)的話比較麻煩,要解決鎖續(xù)租、lua腳本、可重入等一系列復(fù)雜的問(wèn)題。

        對(duì)于單機(jī)模式而言,存在單點(diǎn)問(wèn)題。

        對(duì)于主從架構(gòu)或者哨兵模式,故障轉(zhuǎn)移會(huì)發(fā)生鎖丟失的問(wèn)題,因此產(chǎn)生了紅鎖,但是紅鎖的問(wèn)題也比較多,并不推薦使用,推薦的使用方式是用Redission。

        但是,不管選擇哪種方式,本身對(duì)于Redis來(lái)說(shuō)不是強(qiáng)一致性的,某些極端場(chǎng)景下還是可能會(huì)存在問(wèn)題。

        對(duì)于Zookeeper的實(shí)現(xiàn)方式而言,本身就是保證數(shù)據(jù)一致性的,可靠性更高,所以不存在Redis的各種故障轉(zhuǎn)移帶來(lái)的問(wèn)題,自己實(shí)現(xiàn)也比較簡(jiǎn)單,但是性能相比Redis稍差。

        不過(guò),實(shí)際中我們當(dāng)然是有啥用啥,老板說(shuō)用什么就用什么,我才不管那么多。

        ·················END·················



        往期推薦

        關(guān)于MVCC,我之前寫(xiě)錯(cuò)了,這次我改好了!

        長(zhǎng)篇連載(一)你的編程能力從什么時(shí)候開(kāi)始突飛猛進(jìn)?

        好久沒(méi)更新,新年第一篇!

        好了,我攤牌了,B站,牛逼!

        長(zhǎng)篇連載,人生30年(二):職場(chǎng)菜鳥(niǎo)被開(kāi)除

        《我想進(jìn)大廠》之Zookeeper奪命連環(huán)9問(wèn)


        瀏覽 29
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        評(píng)論
        圖片
        表情
        推薦
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            免费无码视频在线观看 | 男人的天堂视频 | 大香蕉网青青 | 免费三级成人爱做片 | 国产三级韩国三级日本带黄 | 日本三级人妇 | 日本精品三级 | 扒开美女狂揉下部漫画 | 高潮呻吟声 | 国产一区二区精品丝袜 |