雲栖号資訊:【 點選檢視更多行業資訊】
在這裡您可以找到不同行業的第一手的上雲資訊,還在等什麼,快來!
首先分布式鎖和我們平常講到的鎖原理基本一樣,目的就是確定在多個線程并發時,隻有一個線程在同一刻操作這個業務或者說方法、變量。
在一個程序中,也就是一個JVM或者說應用中,我們很容易去處理控制,在jdk java.util并發包中已經為我們提供了這些方法去加鎖,比如synchronized關鍵字或者Lock鎖,都可以處理。
但是我們現在的應用程式如果隻部署一台伺服器,那并發量是很差的,如果同時有上萬的請求那麼很有可能造成伺服器壓力過大,而癱瘓。
想想雙十一和三十晚上十點分支付寶紅包等業務場景,自然需要用到多台伺服器去同時處理這些業務,那麼這些服務可能會有上百台同時處理。
但是請我們大家想一想,如果有100台伺服器要處理分紅包的業務,現在假設有1億的紅包,1千萬個人分,金額随機,那麼這個業務場景下是不是必須確定這1千萬個人最後分的紅包金額總和等于1億。
如果處理不好,每人分到100萬,那馬雲爸爸估計大年初一,就得宣布破産了~~
正常鎖會造成什麼情況?
首先說一下我們為什麼要搞叢集,簡單了解就是,需求量(請求并發量)變大了,一個勞工處理能力有限,那就多招一些勞工來一起處理。
假設1千萬個請求平均配置設定到100台伺服器上,每個伺服器接收10w的請求(這10w個請求并不是在同一秒中來的,可能是在1、2個小時内,可以聯想下我們三十晚上開紅包,等到10點20開始,有的人立馬開了,有的人是不是等到12點了才想起來~)
那這樣的話,平均到每一秒上的請求也就不到1千個,這種壓力一般的伺服器還是可以承受的。
第一個請求到來後,是不是需要在1億裡面給他分一部分錢,金額随機,假設第一個人分到了100,那是不是要在這1億中減去100塊,剩下99999900塊~
第二個使用者再來分,金額随機,這次分200塊,那是不是就需要在剩下的99999900塊中再減去200塊,剩下99999700塊。
等到第10w個使用者來,一看還有1000w,那這1000w全成他的了。
等于是在每個伺服器中去分1億,也就是10w個使用者分了一個億,最後總計有100個伺服器,要分100億。
如果真這樣了,雖說馬雲爸爸不會破産(據最新統計馬雲有2300億人民币),那分紅包的開發項目組,以及産品經理,可以GG了~
簡化結構圖如下:

分布式鎖怎麼去處理?
那麼為了解決這個問題,讓1000萬使用者隻分1億,而不是100億,這個時候分布式鎖就派上用處了。
分布式鎖可以把整個叢集就當作是一個應用一樣去處理,那麼也就需要這個鎖,要獨立于每一個服務之外,而不是在服務裡面。
假設第一個伺服器接收到使用者1的請求後,那麼這個時候,他就不能隻在自己的應用中去判斷還有多少錢可以分了,而需要去外部請求專門負責管理這1億紅包的人(服務),問他:哎,我這裡要分100塊,給我100。
管理紅包的妹子(服務)一看,還有1個億,那好,給你100塊,然後剩下99999900塊。
第二個請求到來後,被伺服器2擷取,繼續去詢問,管理紅包的妹子,我這邊要分10塊,管理紅包的妹子先查了下還有99999900,那就說:好,給你10塊。那就剩下99999890塊。
等到第1000w個請求到來後,伺服器100拿到請求,繼續去詢問,管理紅包的妹子,你要100,妹子翻了翻白眼,對你說,就剩1塊了,愛要不要,那這個時候就隻能給你1塊了(1塊也是錢啊,買根辣條還是可以的)。
這些請求編号1、2不代表執行的先後順序,正式的場景下,應該是100台伺服器每個伺服器持有一個請求去通路負責管理紅包的妹子(服務),那在管紅包的妹子那裡同時會接收到100個請求,這個時候就需要在負責紅包的妹子那裡加個鎖就可以了(抛繡球),你們100個伺服器誰拿到鎖(搶到繡球),誰就進來和我談,我給你分,其他人就等着去吧。
經過上面的分布式鎖的處理後,馬雲爸爸終于放心了,決定給紅包團隊每人加一個雞腿。
簡化的結構圖如下:
分布式鎖的實作有哪些?
說到分布式鎖的實作,還是有很多的,有資料庫方式的,有Redis分布式鎖,有ZooKeeper分布式鎖等等。
我們如果采用Redis作為分布式鎖,那麼上圖中負“責紅包的妹子(服務)”,就可以替換成Redis,請自行腦補。
為什麼Redis可以實作分布式鎖?
首先Redis是單線程的,這裡的單線程指的是網絡請求子產品使用了一個線程(是以不需考慮并發安全性),即一個線程處理所有網絡請求,其他子產品仍用了多個線程。
在實際的操作中過程大緻是這樣子的:
伺服器1要去通路發紅包的妹子,也就是Redis,那麼他會在Redis中通過"setnx key value"操作設定一個key進去,value是啥不重要,重要的是要有一個key,也就是一個标記,而且這個key你愛叫啥叫啥,隻要所有的伺服器設定的key相同就可以。
假設我們設定一個,如下圖:
那麼我們可以看到會傳回一個1,那就代表了成功。
如果再來一個請求去設定同樣的key,如下圖:
這個時候會傳回0,那就代表失敗了。
那麼我們就可以通過這個操作去判斷是不是目前可以拿到鎖,或者說可以去通路“負責發紅包的妹子”,如果傳回1,那我就開始去執行後面的邏輯,如果傳回0,那就說明已經被人占用了,我就要繼續等待。
當伺服器1拿到鎖之後,進行了業務處理,完成後,還需要釋放鎖,如下圖所示:
删除成功傳回1,那麼其他的伺服器就可以繼續重複上面的步驟去設定這個key,以達到擷取鎖的目的。
當然以上的操作是在Redis用戶端直接進行的,通過程式調用的話,肯定就不能這麼寫,比如Java就需要通過Jedis去調用,但是整個處理邏輯基本都是一樣的。
通過上面的方式,我們好像是解決了分布式鎖的問題,但是想想還有沒有什麼問題呢?
對,問題還是有的,可能會有死鎖的問題發生,比如伺服器1設定完之後,擷取了鎖之後,忽然發生了當機。
那後續的删除key操作就沒法執行,這個key會一直在Redis中存在,其他伺服器每次去檢查,都會傳回0,他們都會認為有人在使用鎖,我需要等。
為了解決這個死鎖的問題,我們就就需要給key設定有效期了。
設定的方式有2種:
- 第一種就是在set完key之後,直接設定key的有效期"expire key timeout",為key設定一個逾時時間,機關為second,超過這個時間鎖會自動釋放,避免死鎖。
這種方式相當于,把鎖持有的有效期,交給了Redis去控制。如果時間到了,你還沒有給我删除key,那Redis就直接給你删了,其他伺服器就可以繼續去setnx擷取鎖。
- 第二種方式,就是把删除key權利交給其他的伺服器,那這個時候就需要用到value值了,比如伺服器1,設定了value也就是timeout為目前時間+1秒,這個時候伺服器2通過get發現時間已經超過系統目前時間了,那就說明伺服器1沒有釋放鎖,伺服器1可能出問題了,
伺服器2就開始執行删除key操作,并且繼續執行setnx操作。
但是這塊有一個問題,也就是,不光你伺服器2可能會發現伺服器1逾時了,伺服器3也可能會發現,如果剛好,伺服器2,setnx操作完成,伺服器3就接着删除,是不是伺服器3也可以setnx成功了?
那就等于是伺服器2和伺服器3都拿到鎖了,那就問題大了。這個時候怎麼辦呢?
這個時候需要用到“GETSET key value”指令了。這個指令的意思就是擷取目前key的值,并且設定新的值。
假設伺服器2發現key過期了,開始調用getset指令,然後用擷取的時間判斷是否過期,如果擷取的時間仍然是過期的,那就說明拿到鎖了。
如果沒有,則說明在服務2執行getset之前,伺服器3可能也發現鎖過期了,并且在伺服器2之前執行了getset操作,重新設定了過期時間。
那麼伺服器2就需要放棄後續的操作,繼續等待伺服器3釋放鎖或者去監測key的有效期是否過期。
這塊其實有一個小問題是,伺服器3已經修改了有效期,拿到鎖之後,伺服器2,也修改了有效期,但是沒能拿到鎖,但是這個有效期的時間已經被在伺服器3的基礎上有增加一些,但是這種影響其實還是很小的,幾乎可以忽略不計。
為什麼ZooKeeper可以實作分布式鎖?
百度百科是這麼介紹的:ZooKeeper是一個分布式的,開放源碼的分布式應用程式協調服務,是Google的Chubby一個開源的實作,是Hadoop和Hbase的重要元件。
那對于我們初次認識的人,可以了解成ZooKeeper就像是我們的電腦檔案系統,我們可以在d盤中建立檔案夾a,并且可以繼續在檔案夾a中建立檔案夾a1、a2。
那我們的檔案系統有什麼特點?那就是同一個目錄下檔案名稱不能重複,同樣ZooKeeper也是這樣的。
在ZooKeeper所有的節點,也就是檔案夾稱作Znode,而且這個Znode節點是可以存儲資料的。
我們可以通過“create/zkjjj nice”來建立一個節點,這個指令就表示,在跟目錄下建立一個zkjjj的節點,值是nice。同樣這裡的值,和我在前面說的redis中的一樣,沒什麼意義,你随便給。
另外ZooKeeper可以建立4種類型的節點,分别是:
- 持久性節點
- 持久性順序節點
- 臨時性節點
- 臨時性順序節點
首先說下持久性節點和臨時性節點的差別,持久性節點表示隻要你建立了這個節點,那不管你ZooKeeper的用戶端是否斷開連接配接,ZooKeeper的服務端都會記錄這個節點。
臨時性節點剛好相反,一旦你ZooKeeper用戶端斷開了連接配接,那ZooKeeper服務端就不再儲存這個節點。
再說下順序性節點,順序性節點是指,在建立節點的時候,ZooKeeper會自動給節點編号比如0000001、0000002這種的。
最後說下,ZooKeeper有一個監聽機制,用戶端注冊監聽它關心的目錄節點,當目錄節點發生變化(資料改變、被删除、子目錄節點增加删除)等,ZooKeeper會通知用戶端。
下面我們繼續結合我們上面的分紅包場景,描述下在ZooKeeper中如何加鎖。
假設伺服器1,建立了一個節點/zkjjj,成功了,那伺服器1就擷取了鎖,伺服器2再去建立相同的鎖,那麼他就會失敗,這個時候他就就隻能監聽這個節點的變化。
等到伺服器1,處理完業務,删除了節點後,他就會得到通知,然後去建立同樣的節點,擷取鎖處理業務,再删除節點,後續的100台伺服器與之類似。
注意這裡的100台伺服器并不是挨個去執行上面的建立節點的操作,而是并發的,當伺服器1建立成功,那麼剩下的99個就都會注冊監聽這個節點,等通知,以此類推。
但是大家有沒有注意到,這裡還是有問題的,還是會有死鎖的情況存在,對不對?
當伺服器1建立了節點後挂了,沒能删除,那其他99台伺服器就會一直等通知,那就完蛋了。
這個時候呢,就需要用到臨時性節點了,我們前面說過了,臨時性節點的特點是用戶端一旦斷開,就會丢失,也就是當伺服器1建立了節點後,如果挂了。
那這個節點會自動被删除,這樣後續的其他伺服器,就可以繼續去建立節點,擷取鎖了。
但是我們可能還需要注意到一點,就是驚群效應:舉一個很簡單的例子,當你往一群鴿子中間扔一塊食物,雖然最終隻有一個鴿子搶到食物,但所有鴿子都會被驚動來争奪,沒有搶到……
就是當伺服器1節點有變化,會通知其餘的99個伺服器,但是最終隻有1個伺服器會建立成功,這樣98還是需要等待監聽,那麼為了處理這種情況,就需要用到臨時順序性節點。
大緻意思就是,之前是所有99個伺服器都監聽一個節點,現在就是每一個伺服器監聽自己前面的一個節點。
假設100個伺服器同時發來請求,這個時候會在/zkjjj節點下建立100個臨時順序性節點/zkjjj/000000001,/zkjjj/000000002,一直到/zkjjj/000000100,這個編号就等于是已經給他們設定了擷取鎖的先後順序了。
當001節點處理完畢,删除節點後,002收到通知,去擷取鎖,開始執行,執行完畢,删除節點,通知003~以此類推。
【雲栖号線上課堂】每天都有産品技術專家分享!
課程位址:
https://yqh.aliyun.com/live立即加入社群,與專家面對面,及時了解課程最新動态!
【雲栖号線上課堂 社群】
https://c.tb.cn/F3.Z8gvnK
原文釋出時間:2020-05-11
本文作者:隊長給我球
本文來自:“
dockone”,了解相關資訊可以關注“dockone”