<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          實現(xiàn)分布式鎖

          共 3174字,需瀏覽 7分鐘

           ·

          2021-04-02 18:00

          分布式鎖的實現(xiàn)通常有三種方式,利用MySQL、zookeeper、Redis3種組件實現(xiàn)。

          MySQL實現(xiàn)

          通過MySQL實現(xiàn)分布式鎖相對來說比較好理解,主要思路就是通過主鍵自增長屬性來實現(xiàn),通常也叫AUTO-INC Locking自增長鎖。在InnnoDB存儲引擎的內(nèi)存結(jié)構(gòu)中,對每個含有自增長值的表都有一個自增長計數(shù)器(auto increment counter)。當(dāng)含有自增長計數(shù)器的表進(jìn)行插入操作時,這個計數(shù)器會被初始化,執(zhí)行如下的語句來得到計數(shù)器的值:

          select MAX(auto_inc_col) from t for update;

          插入操作會依據(jù)這個自增長的計數(shù)器值+1賦予自增長序列,這個實現(xiàn)方式稱作AUTO-INC Locking。這種鎖其實是采用一種特殊的表鎖機(jī)制,為了提高插入性能,鎖不是在一個事物完成后才釋放的,而是在完成對自增長序列插入的SQL語句后立即釋放。

          通過偽代碼模擬實現(xiàn)過程:

          參考鏈接:https://juejin.cn/post/6844903688088059912

          對于分布式鎖我們可以創(chuàng)建一個鎖表:


          實現(xiàn)邏輯

          為了達(dá)到可重入鎖的效果那么我們應(yīng)該先進(jìn)行查詢,如果有值,那么需要比較node_info是否一致,這里的node_info可以用機(jī)器IP和線程名字來表示,如果一致那么就加可重入鎖count的值,如果不一致那么就返回false。如果沒有值那么直接插入一條數(shù)據(jù)。需要注意的是這一段代碼需要加事務(wù),必須要保證這一系列操作的原子性。

          阻塞式獲取鎖

          如果獲取不到就睡眠3ms,繼續(xù)獲取直到拿到鎖。

          非阻塞式獲取鎖

          如果獲取不到那么就會馬上返回

          釋放鎖

          unlock的話如果這里的count為1那么可以刪除,如果大于1那么需要減去1。

          總結(jié)

          • 適用場景: Mysql分布式鎖一般適用于資源不存在數(shù)據(jù)庫,如果數(shù)據(jù)庫存在比如訂單,那么可以直接對這條數(shù)據(jù)加行鎖,不需要我們上面多的繁瑣的步驟,比如一個訂單,那么我們可以用select * from order_table where id = 'xxx' for update進(jìn)行加行鎖,那么其他的事務(wù)就不能對其進(jìn)行修改。

          • 優(yōu)點:理解起來簡單,不需要維護(hù)額外的第三方中間件(比如Redis,Zk)。

          • 缺點:雖然容易理解但是實現(xiàn)起來較為繁瑣,需要自己考慮鎖超時,加事務(wù)等等。性能局限于數(shù)據(jù)庫,一般對比緩存來說性能較低。對于高并發(fā)的場景并不是很適合。

          Zookeeper實現(xiàn)

          利用zookeeper的相同路徑下的節(jié)點不能重名的特性和自帶的監(jiān)聽機(jī)制。zookeeper有三類節(jié)點:

          • 持久性節(jié)點:只要創(chuàng)建了節(jié)點,無論客戶端是否斷開鏈接,節(jié)點都會存在。

          • 臨時性節(jié)點:一旦客戶端斷開鏈接,服務(wù)端不再保存該節(jié)點。

          • 順序性節(jié)點:在創(chuàng)建節(jié)點的時候,zookeeper會自動給節(jié)點分配自增長編號,比如/lock/node_000001、/lock/node_000002等。

          /lock是我們用于加鎖的目錄,/resource是我們鎖定的資源,其下面的節(jié)點按照我們加鎖的順序排列。

          通過持久性節(jié)點實現(xiàn)分布式鎖

          實現(xiàn)步驟

          step1:client1創(chuàng)建了resource節(jié)點,創(chuàng)建成功即代表持有了鎖,client2再去創(chuàng)建resource節(jié)點時就會失敗,這個時候只能監(jiān)聽這個節(jié)點的變化。

          step2:client1處理完業(yè)務(wù)后,刪除resource節(jié)點;client2得到通知后再去創(chuàng)建resource節(jié)點,獲取鎖。(多個client會并發(fā)的去競爭創(chuàng)建resource節(jié)點)

          缺點

          • 當(dāng)client1掛掉后沒能刪除resource,那么就出現(xiàn)了死鎖。

          • 存在驚群效應(yīng),當(dāng)client很多時,只有一個client持有鎖,其他所有client都要監(jiān)聽這一個resource節(jié)點。

          通過臨時性節(jié)點實現(xiàn)分布式鎖

          臨時節(jié)點和永久節(jié)點的實現(xiàn)方式一樣,只不過在client1掛掉后,zookeeper會自動刪除resource節(jié)點,相當(dāng)于強(qiáng)制釋放了鎖。這樣就不會出現(xiàn)死鎖的風(fēng)險了。雖然臨時性節(jié)點解決了死鎖的問題,但是沒能解決鯨群效應(yīng)問題。

          通過臨時性順序節(jié)點實現(xiàn)分布式鎖

          在resource鎖資源下按照獲取鎖的順序為每個client維護(hù)一個臨時性的順序節(jié)點,每個節(jié)點只需要監(jiān)聽前一個節(jié)點狀態(tài),這樣只有前一個節(jié)點被刪除后,后面的監(jiān)聽節(jié)點就可以創(chuàng)建resource/xxxxx資源了。這樣就解決了驚群效應(yīng)鎖帶來的問題了。

          Curator

          Curator封裝了Zookeeper底層的Api,使我們更加容易方便的對Zookeeper進(jìn)行操作,并且它封裝了分布式鎖的功能,這樣我們就不需要再自己實現(xiàn)了。

          Curator實現(xiàn)了可重入鎖(InterProcessMutex),也實現(xiàn)了不可重入鎖(InterProcessSemaphoreMutex)。在可重入鎖中還實現(xiàn)了讀寫鎖。

          Redis實現(xiàn)

          redis是單線程受理請求的。通過Redis Setnx(SET if Not eXists) 命令在指定的 key 不存在時,為 key 設(shè)置指定的值。設(shè)置成功,返回 1 。設(shè)置失敗,返回 0 。當(dāng)返回結(jié)果為1時我們可以認(rèn)為該client持有鎖,當(dāng)client處理完業(yè)務(wù)后執(zhí)行del key刪除該鍵相當(dāng)于釋放鎖。

          死鎖

          這種方式雖然可以實現(xiàn)分布式鎖,但是也存在死鎖問題。如果持鎖的client掛掉了,此時該key會一直存在其他client就獲取不到鎖了。所以對key要加入過期時間限制,加入過期時間需要和setNx同一個原子操作,在Redis2.8之前我們需要使用Lua腳本達(dá)到我們的目的,但是redis2.8之后redis支持nx和ex操作是同一原子操作。

          set resourceName value ex 5 nx

          鎖失效

          雖然通過給key設(shè)置有效期能解決死鎖問題,但是又引發(fā)了一個新問題鎖失效,就是如果client1不是掛掉了,而是業(yè)務(wù)處理耗時長,在5ms之后Redis主動把這個key刪除了,那么另外的client2就可以獲取到鎖了,此時就存在了兩個client持有鎖的情況。這個時候其實可以把超時設(shè)置這一環(huán)節(jié)交給各個client來完成,具體思路就是客戶端client1創(chuàng)建一個key的時候,設(shè)置對應(yīng)的value為超時時間,可以用當(dāng)前時間戳+(過期窗口),這樣當(dāng)client2獲取到這個值之后發(fā)現(xiàn)當(dāng)前系統(tǒng)時間已經(jīng)超過value了,那么代表client1沒有釋放鎖,這個時候client2需要通過Redis提供的 GETSET KEY VALUE來獲取鎖并重置過期時間,之所以要用到GETSET就是為了在client2獲取鎖之后處理業(yè)務(wù)的時候,client3、client4也能夠發(fā)現(xiàn)key過期了并獲取到了鎖的問題。

          Redission

          Javaer都知道Jedis,Jedis是Redis的Java實現(xiàn)的客戶端,其API提供了比較全面的Redis命令的支持。Redission也是Redis的客戶端,相比于Jedis功能簡單。Jedis簡單使用阻塞的I/O和redis交互,Redission通過Netty支持非阻塞I/O。Jedis最新版本2.9.0是2016年的快3年了沒有更新,而Redission最新版本是2018.10月更新。Redission封裝了鎖的實現(xiàn),其繼承了java.util.concurrent.locks.Lock的接口,讓我們像操作我們的本地Lock一樣去操作Redission的Lock。


          瀏覽 86
          點贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  亚洲首页欧美美女爱爱首页 | 久久性生活视频 | 九九九九九九九九九九九九九九十九 免费 琪琪先锋 torrent magnet | 操BAV网| 国庄三级HD中文久久精品 |