作者:賈俊江 ,JAVA開(kāi)發(fā)工程師, 一個(gè)不止會(huì)寫代碼的程序員,還是一個(gè)會(huì)打籃球的程序員。熱衷于實(shí)用技術(shù)便捷方案的探索,目前在研究分布式架構(gòu)相關(guān)技術(shù)。
樂(lè)安ssl適用于網(wǎng)站、小程序/APP、API接口等需要進(jìn)行數(shù)據(jù)傳輸應(yīng)用場(chǎng)景,ssl證書未來(lái)市場(chǎng)廣闊!成為成都創(chuàng)新互聯(lián)公司的ssl證書銷售渠道,可以享受市場(chǎng)價(jià)格4-6折優(yōu)惠!如果有意向歡迎電話聯(lián)系或者加微信:028-86922220(備注:SSL證書合作)期待與您的合作!
來(lái)自:博客園,鏈接:https://www.cnblogs.com/JJJ1990/p/10496850.html
首先,分布式鎖和我們平常講到的鎖原理基本一樣,目的就是確保在多個(gè)線程并發(fā)時(shí),只有一個(gè)線程在同一刻操作這個(gè)業(yè)務(wù)或者說(shuō)方法、變量。
在一個(gè)進(jìn)程中,也就是一個(gè)jvm或者說(shuō)應(yīng)用中,我們很容易去處理控制,在jdk java.util并發(fā)包中已經(jīng)為我們提供了這些方法去加鎖,比如synchronized關(guān)鍵字或者Lock鎖,都可以處理。
但是我們現(xiàn)在的應(yīng)用程序如果只部署一臺(tái)服務(wù)器,那并發(fā)量是很差的,如果同時(shí)有上萬(wàn)的請(qǐng)求,很有可能造成服務(wù)器壓力過(guò)大而癱瘓。想想雙十一和大年三十晚上十點(diǎn),瓜分支付寶紅包等業(yè)務(wù)場(chǎng)景,自然需要用到多臺(tái)服務(wù)器去同時(shí)處理這些業(yè)務(wù),這些服務(wù)可能會(huì)有上百臺(tái)同時(shí)處理。
但是我們想一想,如果有100臺(tái)服務(wù)器要處理分紅包的業(yè)務(wù),現(xiàn)在假設(shè)有1億的紅包,1千萬(wàn)個(gè)人分,金額隨機(jī),那么這個(gè)業(yè)務(wù)場(chǎng)景下,是不是必須確保這1千萬(wàn)個(gè)人最后分的紅包金額總和等于1億?
如果處理不好~~每人分到100萬(wàn),那馬云爸爸估計(jì)大年初一,就得宣布破產(chǎn)了~~
首先說(shuō)一下我們?yōu)槭裁匆慵骸?/p>
簡(jiǎn)單理解就是,需求量(請(qǐng)求并發(fā)量)變大了,一個(gè)工人處理能力有限,那就多招一些工人來(lái)一起處理。
假設(shè)1千萬(wàn)個(gè)請(qǐng)求平均分配到100臺(tái)服務(wù)器上,每個(gè)服務(wù)器接收10w的請(qǐng)求。這10w個(gè)請(qǐng)求并不是在同一秒中來(lái)的,可能是在1,2個(gè)小時(shí)內(nèi),可以聯(lián)想下我們?nèi)砩祥_(kāi)紅包,等到10:20開(kāi)始,有的人立馬開(kāi)了,有的人等到12點(diǎn)才想起來(lái)。
那這樣的話,平均到每一秒上的請(qǐng)求也就不到1千個(gè),這種壓力一般的服務(wù)器還是可以承受的。
第一個(gè)用戶來(lái)分,請(qǐng)求到來(lái)后,需要在1億里面給他分一部分錢,金額隨機(jī),假設(shè)第一個(gè)人分到了100,那就要在這1億中減去100塊,剩下99999900塊~
第二個(gè)用戶再來(lái)分,金額隨機(jī),這次分200塊,那就需要在剩下的99999900塊中再減去200塊,剩下99999700塊。
等到第10w個(gè)用戶來(lái),一看還有1000w,那這1000w全成他的了。
等于是在每個(gè)服務(wù)器中去分1億,也就是10w個(gè)用戶分了一個(gè)億,最后總計(jì)有100個(gè)服務(wù)器,要分100億。
如果真這樣了,雖說(shuō)馬云爸爸不會(huì)破產(chǎn)(據(jù)最新統(tǒng)計(jì)馬云有2300億人民幣),那分紅包的開(kāi)發(fā)項(xiàng)目組,以及產(chǎn)品經(jīng)理,可以GG了~
簡(jiǎn)化結(jié)構(gòu)圖如下:
那么為了解決這個(gè)問(wèn)題,讓1000萬(wàn)用戶只分1億,而不是100億,這個(gè)時(shí)候分布式鎖就派上用處了。
分布式鎖可以把整個(gè)集群就當(dāng)作是一個(gè)應(yīng)用一樣去處理,那么也就需要這個(gè)鎖獨(dú)立于每一個(gè)服務(wù)之外,而不是在服務(wù)里面。
假設(shè)第一個(gè)服務(wù)器接收到用戶1的請(qǐng)求后,不能只在自己的應(yīng)用中去判斷還有多少錢可以分了,而需要去外部請(qǐng)求專門負(fù)責(zé)管理這1億紅包的人(服務(wù)),問(wèn)他:哎,我這里要分100塊,給我100。
管理紅包的妹子(服務(wù))一看,還有1個(gè)億,那好,給你100塊,然后剩下99999900塊。
第二個(gè)請(qǐng)求到來(lái)后,被服務(wù)器2獲取,繼續(xù)去詢問(wèn),管理紅包的妹子,我這邊要分10塊,管理紅包的妹子先查了下還有99999900,那就說(shuō):好,給你10塊。那就剩下99999890塊。
等到第1000w個(gè)請(qǐng)求到來(lái)后,服務(wù)器100拿到請(qǐng)求,繼續(xù)去詢問(wèn),管理紅包的妹子,我要100,妹子翻了翻白眼,對(duì)你說(shuō),就剩1塊了,愛(ài)要不要,那這個(gè)時(shí)候就只能給你1塊了(1塊也是錢啊,買根辣條還是可以的)。
這些請(qǐng)求編號(hào)1,2不代表執(zhí)行的先后順序,正式的場(chǎng)景下,應(yīng)該是100臺(tái)服務(wù)器每個(gè)服務(wù)器持有一個(gè)請(qǐng)求去訪問(wèn)負(fù)責(zé)管理紅包的妹子(服務(wù)),那在管紅包的妹子那里同時(shí)會(huì)接收到100個(gè)請(qǐng)求,這個(gè)時(shí)候就需要在負(fù)責(zé)紅包的妹子那里加個(gè)鎖就可以了(拋繡球),你們100個(gè)服務(wù)器誰(shuí)拿到鎖(搶到繡球),誰(shuí)就進(jìn)來(lái)和我談,我給你分,其他人就等著去吧。
經(jīng)過(guò)上面的分布式鎖的處理后,馬云爸爸終于放心了,決定給紅包團(tuán)隊(duì)每人加一個(gè)雞腿。
簡(jiǎn)化的結(jié)構(gòu)圖如下:
說(shuō)到分布式鎖的實(shí)現(xiàn),還是有很多的,有數(shù)據(jù)庫(kù)方式的,有redis分布式鎖,有Zookeeper分布式鎖等等。
我們?nèi)绻捎肦edis作為分布式鎖,那么上圖中負(fù)“責(zé)紅包的妹子(服務(wù))”,就可以替換成Redis,請(qǐng)自行腦補(bǔ)。
首先Redis是單線程的,這里的單線程指的是網(wǎng)絡(luò)請(qǐng)求模塊使用了一個(gè)線程(所以不需考慮并發(fā)安全性),即一個(gè)線程處理所有網(wǎng)絡(luò)請(qǐng)求,其他模塊仍用了多個(gè)線程。
在實(shí)際的操作中過(guò)程大致是這樣子的:
服務(wù)器1要去訪問(wèn)發(fā)紅包的妹子,也就是Redis,那么他會(huì)在Redis中通過(guò)"setnx key value" 操作設(shè)置一個(gè)key進(jìn)去,value是啥不重要,重要的是要有一個(gè)key,也就是一個(gè)標(biāo)記,而且這個(gè)key你愛(ài)叫啥叫啥,只要所有的服務(wù)器設(shè)置的key相同就可以。
假設(shè)我們?cè)O(shè)置一個(gè),如下圖:
那么我們可以看到會(huì)返回一個(gè)1,那就代表了成功。
如果再來(lái)一個(gè)請(qǐng)求去設(shè)置同樣的key,如下圖:
這個(gè)時(shí)候會(huì)返回0,那就代表失敗了。
那么我們就可以通過(guò)這個(gè)操作去判斷是不是當(dāng)前可以拿到鎖,或者說(shuō)可以去訪問(wèn)“負(fù)責(zé)發(fā)紅包的妹子”,如果返回1,那我就開(kāi)始去執(zhí)行后面的邏輯,如果返回0,那就說(shuō)明已經(jīng)被人占用了,我就要繼續(xù)等待。
當(dāng)服務(wù)器1拿到鎖之后,進(jìn)行了業(yè)務(wù)處理,完成后,還需要釋放鎖,如下圖所示:
刪除成功返回1,那么其他的服務(wù)器就可以繼續(xù)重復(fù)上面的步驟去設(shè)置這個(gè)key,以達(dá)到獲取鎖的目的。
當(dāng)然以上的操作是在Redis客戶端直接進(jìn)行的,通過(guò)程序調(diào)用的話,肯定就不能這么寫,比如java就需要通過(guò)jedis去調(diào)用,但是整個(gè)處理邏輯基本都是一樣的。
通過(guò)上面的方式,我們好像是解決了分布式鎖的問(wèn)題,但是想想還有沒(méi)有什么問(wèn)題呢?
對(duì),問(wèn)題還是有的,可能會(huì)有死鎖的問(wèn)題發(fā)生,比如服務(wù)器1設(shè)置完之后,獲取了鎖之后,忽然發(fā)生了宕機(jī)。
那后續(xù)的刪除key操作就沒(méi)法執(zhí)行,這個(gè)key會(huì)一直在Redis中存在,其他服務(wù)器每次去檢查,都會(huì)返回0,他們都會(huì)認(rèn)為有人在使用鎖,我需要等。
為了解決這個(gè)死鎖的問(wèn)題,我們就需要給key設(shè)置有效期了。
設(shè)置的方式有2種:
第一種就是在set完key之后,直接設(shè)置key的有效期 "expire key timeout" ,為key設(shè)置一個(gè)超時(shí)時(shí)間,單位為second,超過(guò)這個(gè)時(shí)間鎖會(huì)自動(dòng)釋放,避免死鎖。
這種方式相當(dāng)于,把鎖持有的有效期,交給了Redis去控制。如果時(shí)間到了,你還沒(méi)有給我刪除key,那Redis就直接給你刪了,其他服務(wù)器就可以繼續(xù)去setnx獲取鎖。
第二種方式,就是把刪除key權(quán)利交給其他的服務(wù)器,那這個(gè)時(shí)候就需要用到value值了, 比如服務(wù)器1,設(shè)置了value也就是timeout為當(dāng)前時(shí)間+1秒 ,這個(gè)時(shí)候服務(wù)器2通過(guò)get發(fā)現(xiàn)時(shí)間已經(jīng)超過(guò)系統(tǒng)當(dāng)前時(shí)間了,那就說(shuō)明服務(wù)器1沒(méi)有釋放鎖,服務(wù)器1可能出問(wèn)題了,服務(wù)器2就開(kāi)始執(zhí)行刪除key操作,并且繼續(xù)執(zhí)行setnx操作。
但是這塊有一個(gè)問(wèn)題,也就是不光你服務(wù)器2可能會(huì)發(fā)現(xiàn)服務(wù)器1超時(shí)了,服務(wù)器3也可能會(huì)發(fā)現(xiàn),如果剛好服務(wù)器2 setnx操作完成,服務(wù)器3就接著刪除,是不是服務(wù)器3也可以setnx成功了?
那就等于是服務(wù)器2和服務(wù)器3都拿到鎖了,那就問(wèn)題大了。這個(gè)時(shí)候怎么辦呢?
這個(gè)時(shí)候需要用到“GETSET key value”命令了。這個(gè)命令的意思就是獲取當(dāng)前key的值,并且設(shè)置新的值。
假設(shè)服務(wù)器2發(fā)現(xiàn)key過(guò)期了,開(kāi)始調(diào)用getset命令,然后用獲取的時(shí)間判斷是否過(guò)期,如果獲取的時(shí)間仍然是過(guò)期的,那就說(shuō)明拿到鎖了。
如果沒(méi)有,則說(shuō)明在服務(wù)2執(zhí)行g(shù)etset之前,服務(wù)器3可能也發(fā)現(xiàn)鎖過(guò)期了,并且在服務(wù)器2之前執(zhí)行了getset操作,重新設(shè)置了過(guò)期時(shí)間。
那么服務(wù)器2就需要放棄后續(xù)的操作,繼續(xù)等待服務(wù)器3釋放鎖或者去監(jiān)測(cè)key的有效期是否過(guò)期。
這塊其實(shí)有一個(gè)小問(wèn)題是,服務(wù)器3已經(jīng)修改了有效期,拿到鎖之后,服務(wù)器2也修改了有效期,但是沒(méi)能拿到鎖,但是這個(gè)有效期的時(shí)間已經(jīng)被在服務(wù)器3的基礎(chǔ)上有增加一些,但是這種影響其實(shí)還是很小的,幾乎可以忽略不計(jì)。
百度百科是這么介紹的:ZooKeeper是一個(gè)分布式的,開(kāi)放源碼的分布式應(yīng)用程序協(xié)調(diào)服務(wù),是Google的Chubby一個(gè)開(kāi)源的實(shí)現(xiàn),是Hadoop和Hbase的重要組件。
那對(duì)于我們初次認(rèn)識(shí)的人,可以理解成ZooKeeper就像是我們的電腦文件系統(tǒng),我們可以在d盤中創(chuàng)建文件夾a,并且可以繼續(xù)在文件夾a中創(chuàng)建文件夾a1,a2。
那我們的文件系統(tǒng)有什么特點(diǎn)?那就是同一個(gè)目錄下文件名稱不能重復(fù),同樣ZooKeeper也是這樣的。
在ZooKeeper所有的節(jié)點(diǎn),也就是文件夾稱作Znode,而且這個(gè)Znode節(jié)點(diǎn)是可以存儲(chǔ)數(shù)據(jù)的。
我們可以通過(guò)“ create /zkjjj nice”來(lái)創(chuàng)建一個(gè)節(jié)點(diǎn),這個(gè)命令就表示,在跟目錄下創(chuàng)建一個(gè)zkjjj的節(jié)點(diǎn),值是nice。同樣這里的值,和我在前面說(shuō)的Redis中的一樣,沒(méi)什么意義,你隨便給。
另外ZooKeeper可以創(chuàng)建4種類型的節(jié)點(diǎn),分別是:
持久性節(jié)點(diǎn)
持久性順序節(jié)點(diǎn)
臨時(shí)性節(jié)點(diǎn)
臨時(shí)性順序節(jié)點(diǎn)
首先說(shuō)下持久性節(jié)點(diǎn)和臨時(shí)性節(jié)點(diǎn)的區(qū)別:
持久性節(jié)點(diǎn)表示只要你創(chuàng)建了這個(gè)節(jié)點(diǎn),那不管你ZooKeeper的客戶端是否斷開(kāi)連接,ZooKeeper的服務(wù)端都會(huì)記錄這個(gè)節(jié)點(diǎn);
臨時(shí)性節(jié)點(diǎn)剛好相反,一旦你ZooKeeper客戶端斷開(kāi)了連接,那ZooKeeper服務(wù)端就不再保存這個(gè)節(jié)點(diǎn);
順便也說(shuō)下順序性節(jié)點(diǎn),順序性節(jié)點(diǎn)是指,在創(chuàng)建節(jié)點(diǎn)的時(shí)候,ZooKeeper會(huì)自動(dòng)給節(jié)點(diǎn)編號(hào)比如0000001,0000002這種的。
Zookeeper有一個(gè)監(jiān)聽(tīng)機(jī)制,客戶端注冊(cè)監(jiān)聽(tīng)它關(guān)心的目錄節(jié)點(diǎn),當(dāng)目錄節(jié)點(diǎn)發(fā)生變化(數(shù)據(jù)改變、被刪除、子目錄節(jié)點(diǎn)增加刪除)等,Zookeeper會(huì)通知客戶端。
下面我們繼續(xù)結(jié)合我們上面的分紅包場(chǎng)景,描述下在Zookeeper中如何加鎖。
假設(shè)服務(wù)器1,創(chuàng)建了一個(gè)節(jié)點(diǎn) /zkjjj,成功了,那服務(wù)器1就獲取了鎖,服務(wù)器2再去創(chuàng)建相同的鎖,就會(huì)失敗,這個(gè)時(shí)候就只能監(jiān)聽(tīng)這個(gè)節(jié)點(diǎn)的變化。
等到服務(wù)器1處理完業(yè)務(wù),刪除了節(jié)點(diǎn)后,他就會(huì)得到通知,然后去創(chuàng)建同樣的節(jié)點(diǎn),獲取鎖處理業(yè)務(wù),再刪除節(jié)點(diǎn),后續(xù)的100臺(tái)服務(wù)器與之類似。
注意這里的100臺(tái)服務(wù)器并不是挨個(gè)去執(zhí)行上面的創(chuàng)建節(jié)點(diǎn)的操作,而是并發(fā)的,當(dāng)服務(wù)器1創(chuàng)建成功,那么剩下的99個(gè)就都會(huì)注冊(cè)監(jiān)聽(tīng)這個(gè)節(jié)點(diǎn),等通知,以此類推。
但是大家有沒(méi)有注意到,這里還是有問(wèn)題的,還是會(huì)有死鎖的情況存在,對(duì)不對(duì)?
當(dāng)服務(wù)器1創(chuàng)建了節(jié)點(diǎn)后掛了,沒(méi)能刪除,那其他99臺(tái)服務(wù)器就會(huì)一直等通知,那就完蛋了。。。
這個(gè)時(shí)候就需要用到臨時(shí)性節(jié)點(diǎn)了,我們前面說(shuō)過(guò)了,臨時(shí)性節(jié)點(diǎn)的特點(diǎn)是客戶端一旦斷開(kāi),就會(huì)丟失,也就是當(dāng)服務(wù)器1創(chuàng)建了節(jié)點(diǎn)后,如果掛了,那這個(gè)節(jié)點(diǎn)會(huì)自動(dòng)被刪除,這樣后續(xù)的其他服務(wù)器,就可以繼續(xù)去創(chuàng)建節(jié)點(diǎn),獲取鎖了。
但是我們可能還需要注意到一點(diǎn),就是驚群效應(yīng):舉一個(gè)很簡(jiǎn)單的例子,當(dāng)你往一群鴿子中間扔一塊食物,雖然最終只有一個(gè)鴿子搶到食物,但所有鴿子都會(huì)被驚動(dòng)來(lái)爭(zhēng)奪,沒(méi)有搶到…
就是當(dāng)服務(wù)器1節(jié)點(diǎn)有變化,會(huì)通知其余的99個(gè)服務(wù)器,但是最終只有1個(gè)服務(wù)器會(huì)創(chuàng)建成功,這樣98還是需要等待監(jiān)聽(tīng),那么為了處理這種情況,就需要用到臨時(shí)順序性節(jié)點(diǎn)。大致意思就是,之前是所有99個(gè)服務(wù)器都監(jiān)聽(tīng)一個(gè)節(jié)點(diǎn),現(xiàn)在就是每一個(gè)服務(wù)器監(jiān)聽(tīng)自己前面的一個(gè)節(jié)點(diǎn)。
假設(shè)100個(gè)服務(wù)器同時(shí)發(fā)來(lái)請(qǐng)求,這個(gè)時(shí)候會(huì)在/zkjjj節(jié)點(diǎn)下創(chuàng)建100個(gè)臨時(shí)順序性節(jié)點(diǎn)/zkjjj/000000001,/zkjjj/000000002,一直到/zkjjj/000000100,這個(gè)編號(hào)就等于是已經(jīng)給他們?cè)O(shè)置了獲取鎖的先后順序了。
當(dāng)001節(jié)點(diǎn)處理完畢,刪除節(jié)點(diǎn)后,002收到通知,去獲取鎖,開(kāi)始執(zhí)行,執(zhí)行完畢,刪除節(jié)點(diǎn),通知003~以此類推。