分布式鎖的幾種實(shí)現(xiàn)方式
概述
目前幾乎很多大型網(wǎng)站及應(yīng)用都是分布式部署的尽纽,分布式場景中的數(shù)據(jù)一致性問題一直是一個比較重要的話題奥喻。分布式的CAP理論告訴我們“任何一個分布式系統(tǒng)都無法同時滿足一致性(Consistency)、可用性(Availability)和分區(qū)容錯性(Partition tolerance)铭段,最多只能同時滿足兩項(xiàng)惯裕。”所以撞牢,很多系統(tǒng)在設(shè)計(jì)之初就要對這三者做出取舍率碾。在互聯(lián)網(wǎng)領(lǐng)域的絕大多數(shù)的場景中,都需要犧牲強(qiáng)一致性來換取系統(tǒng)的高可用性屋彪,系統(tǒng)往往只需要保證“最終一致性”所宰,只要這個最終時間是在用戶可以接受的范圍內(nèi)即可。
在很多場景中畜挥,我們?yōu)榱吮WC數(shù)據(jù)的最終一致性仔粥,需要很多的技術(shù)方案來支持,比如分布式事務(wù)、分布式鎖等躯泰。有的時候谭羔,我們需要保證一個方法在同一時間內(nèi)只能被同一個線程執(zhí)行。在單機(jī)環(huán)境中麦向,Java中其實(shí)提供了很多并發(fā)處理相關(guān)的API瘟裸,但是這些API在分布式場景中就無能為力了。也就是說單純的Java Api并不能提供分布式鎖的能力诵竭。所以針對分布式鎖的實(shí)現(xiàn)目前有多種方案话告。
目前針對分布式鎖的實(shí)現(xiàn),比較常用的有以下幾種方案:
1. 基于數(shù)據(jù)庫實(shí)現(xiàn)分布式鎖
2. 基于緩存(redis卵慰,memcached沙郭,tair)實(shí)現(xiàn)分布式鎖
3. 基于Zookeeper實(shí)現(xiàn)分布式鎖。
在分析這幾種實(shí)現(xiàn)方案之前我們先來想一下裳朋,我們需要的分布式鎖應(yīng)該是怎么樣的病线?(這里以方法鎖為例,資源鎖同理)
1. 可以保證在分布式部署的應(yīng)用集群中再扭,同一個方法在同一時間只能被一臺機(jī)器上的一個線程執(zhí)行氧苍。
2. 這把鎖要是一把可重入鎖(避免死鎖)
3. 這把鎖最好是一把阻塞鎖(根據(jù)業(yè)務(wù)需求考慮要不要這條)
4. 有高可用的獲取鎖和釋放鎖功能
5. 獲取鎖和釋放鎖的性能要好
換句話說,為了分布式鎖可用泛范,我們至少要確保鎖的實(shí)現(xiàn)必須同時滿足以下四個條件才行:
1. 互斥性:
在任意時刻让虐,有且只有一個客戶端能夠持有鎖。
2. 不會發(fā)生死鎖:
即使某個客戶端在持有鎖的期間崩潰掛掉沒有及時主動解鎖罢荡,也能夠確保后續(xù)其他客戶端能夠正常加鎖赡突。
3. 具有容錯性:
只要大部分的節(jié)點(diǎn)正常運(yùn)行,客戶端就能夠正常的進(jìn)行加鎖和解鎖操作
4. 解鈴還須系鈴人:
加鎖和解鎖必須保證是同一個客戶端区赵,A客戶端不能隨隨便便地就把B客戶端加的鎖給解掉了惭缰。
基于數(shù)據(jù)庫實(shí)現(xiàn)分布式鎖
1、基于數(shù)據(jù)庫表
要實(shí)現(xiàn)分布式鎖笼才,最簡單的方式可能就是直接創(chuàng)建一張鎖表漱受,然后通過操作該表中的數(shù)據(jù)來實(shí)現(xiàn)了。當(dāng)我們要鎖住某個方法或資源時骡送,我們就在該表中增加一條記錄昂羡,想要釋放鎖的時候就刪除這條記錄。
1摔踱、創(chuàng)建這樣一張數(shù)據(jù)庫表:
CREATE TABLE `methodLock`(
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主鍵',
`method_name` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '鎖定的方法名',
`desc` varchar(1024) NOT NULL DEFAULT '備注信息',
`update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '保存數(shù)據(jù)時間虐先,自動生成',
PRIMARY KEY (`id`),
UNIQUE KEY `uidx_method_name` (`method_name`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='鎖定的方法';
2、當(dāng)我們想要鎖住某個方法時派敷,執(zhí)行以下SQL:
## 對某個方法加鎖
insert into methodLock(method_name, desc) VALUES('method_name', 'desc');
因?yàn)槲覀儗ethod_name做了唯一性約束蛹批,這里如果有多個請求同時提交到數(shù)據(jù)庫的話撰洗,數(shù)據(jù)庫會保證只有一個操作可以成功,那么我們就可以認(rèn)為操作成功的那個線程獲得了該方法的鎖腐芍,可以執(zhí)行方法體內(nèi)容差导。
3、當(dāng)方法執(zhí)行完畢之后甸赃,想要釋放鎖的話柿汛,需要執(zhí)行以下Sql:
## 方法執(zhí)行完畢釋放鎖
delete from methodLock where method_name = 'method_name';
上面這種簡單的實(shí)現(xiàn)有以下幾個問題:
1. 這把鎖強(qiáng)依賴數(shù)據(jù)庫的可用性,數(shù)據(jù)庫是一個單點(diǎn)埠对,一旦數(shù)據(jù)庫掛掉络断,會導(dǎo)致業(yè)務(wù)系統(tǒng)不可用。
2. 這把鎖沒有失效時間项玛,一旦解鎖操作失敗貌笨,就會導(dǎo)致鎖記錄一直在數(shù)據(jù)庫中,其他線程無法再獲得到鎖襟沮。
3. 這把鎖只能是非阻塞的锥惋,因?yàn)閿?shù)據(jù)的insert操作,一旦插入失敗就會直接報(bào)錯开伏。沒有獲得鎖的線程并不會進(jìn)入排隊(duì)隊(duì)列膀跌,要想再次獲得鎖就要再次觸發(fā)獲得鎖操作。
4. 這把鎖是非重入的固灵,同一個線程在沒有釋放鎖之前無法再次獲得該鎖捅伤。因?yàn)閿?shù)據(jù)中數(shù)據(jù)已經(jīng)存在了。
當(dāng)然巫玻,我們也可以有其他方式解決上面的問題丛忆。
1. 數(shù)據(jù)庫是單點(diǎn)?
搞兩個數(shù)據(jù)庫仍秤,數(shù)據(jù)之前雙向同步熄诡。一旦掛掉快速切換到備庫上。
2. 沒有失效時間诗力?
只要做一個定時任務(wù)凰浮,每隔一定時間把數(shù)據(jù)庫中的超時數(shù)據(jù)清理一遍。
3. 非阻塞的苇本?
搞一個while循環(huán)导坟,直到insert成功再返回成功。
4. 非重入的圈澈?
在數(shù)據(jù)庫表中加個字段,記錄當(dāng)前獲得鎖的機(jī)器的主機(jī)信息和線程信息尘惧,那么下次再獲取鎖的時候先查詢數(shù)據(jù)庫康栈,如果當(dāng)前機(jī)器的主機(jī)信息和線程信息在數(shù)據(jù)庫可以查到的話,直接把鎖分配給他就可以了。
2啥么、基于數(shù)據(jù)庫排他鎖
除了可以通過增刪操作數(shù)據(jù)表中的記錄以外登舞,其實(shí)還可以借助數(shù)據(jù)中自帶的鎖來實(shí)現(xiàn)分布式的鎖。我們還用剛剛創(chuàng)建的那張數(shù)據(jù)庫表悬荣〔っ耄可以通過數(shù)據(jù)庫的排他鎖來實(shí)現(xiàn)分布式鎖。
基于MySql的InnoDB引擎氯迂,可以使用以下方法來實(shí)現(xiàn)加鎖操作践叠,偽代碼如下:
public boolean lock(){
connection.setAutoCommit(false);
while (true){
try {
result = "select * from methodLock where method_name = 'xxx' for update";
if(null == result){
return true;
}
} catch (Exception e) {
e.printStackTrace();
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return false;
}
在查詢語句后面增加for update,數(shù)據(jù)庫會在查詢過程中給數(shù)據(jù)庫表增加排他鎖(這里再多提一句嚼蚀,InnoDB引擎在加鎖的時候禁灼,只有通過索引進(jìn)行檢索的時候才會使用行級鎖,否則會使用表級鎖轿曙。這里我們希望使用行級鎖弄捕,就要給method_name添加索引,值得注意的是导帝,這個索引一定要創(chuàng)建成唯一索引守谓,否則會出現(xiàn)多個重載方法之間無法同時被訪問的問題。重載方法的話建議把參數(shù)類型也加上您单。)斋荞。當(dāng)某條記錄被加上排他鎖之后,其他線程無法再在該行記錄上增加排他鎖睹限。
我們可以認(rèn)為獲得排它鎖的線程即可獲得分布式鎖譬猫,當(dāng)獲取到鎖之后,可以執(zhí)行方法的業(yè)務(wù)邏輯羡疗,執(zhí)行完方法之后染服,再通過以下方法解鎖,偽代碼如下:
public void unLock(){
try {
connection.commit();
} catch (SQLException e) {
e.printStackTrace();
}
}
通過connection.commit()操作來釋放鎖叨恨。這種方法可以有效的解決上面提到的無法釋放鎖和阻塞鎖的問題柳刮。
1. 阻塞鎖?
for update語句會在執(zhí)行成功后立即返回痒钝,在執(zhí)行失敗時一直處于阻塞狀態(tài)秉颗,直到成功。
2. 鎖定之后服務(wù)宕機(jī)送矩,無法釋放蚕甥?
使用這種方式,服務(wù)宕機(jī)之后數(shù)據(jù)庫會自己把鎖釋放掉栋荸。
但是還是無法直接解決數(shù)據(jù)庫單點(diǎn)和可重入問題菇怀。
這里還可能存在另外一個問題凭舶,雖然我們對method_name 使用了唯一索引,并且顯示使用for update來使用行級鎖爱沟。但是帅霜,MySql會對查詢進(jìn)行優(yōu)化,即便在條件中使用了索引字段呼伸,但是否使用索引來檢索數(shù)據(jù)是由 MySQL 通過判斷不同執(zhí)行計(jì)劃的代價來決定的身冀,如果 MySQL 認(rèn)為全表掃效率更高,比如對一些很小的表括享,它就不會使用索引搂根,這種情況下 InnoDB 將使用表鎖,而不是行鎖奶浦。如果發(fā)生這種情況就悲劇了兄墅。。澳叉。
還有一個問題隙咸,就是我們要使用排他鎖來進(jìn)行分布式鎖的lock,那么一個排他鎖長時間不提交成洗,就會占用數(shù)據(jù)庫連接五督。一旦類似的連接變得多了,就可能把數(shù)據(jù)庫連接池?fù)伪?/p>
總結(jié)
- 總結(jié)一下使用數(shù)據(jù)庫來實(shí)現(xiàn)分布式鎖的方式瓶殃,這兩種方式都是依賴數(shù)據(jù)庫的一張表充包,一種是通過表中的記錄的存在情況確定當(dāng)前是否有鎖存在,另外一種是通過數(shù)據(jù)庫的排他鎖來實(shí)現(xiàn)分布式鎖遥椿。
- 數(shù)據(jù)庫實(shí)現(xiàn)分布式鎖的優(yōu)點(diǎn)
- 直接借助數(shù)據(jù)庫基矮,容易理解。
- 數(shù)據(jù)庫實(shí)現(xiàn)分布式鎖的缺點(diǎn)
- 會有各種各樣的問題冠场,在解決問題的過程中會使整個方案變得越來越復(fù)雜家浇。
- 操作數(shù)據(jù)庫需要一定的開銷,性能問題需要考慮碴裙。
- 使用數(shù)據(jù)庫的行級鎖并不一定靠譜钢悲,尤其是當(dāng)我們的鎖表并不大的時候。
基于緩存實(shí)現(xiàn)分布式鎖
相比較于基于數(shù)據(jù)庫實(shí)現(xiàn)分布式鎖的方案來說舔株,基于緩存來實(shí)現(xiàn)在性能方面會表現(xiàn)的更好一點(diǎn)莺琳。而且很多緩存是可以集群部署的,可以解決單點(diǎn)問題载慈。
目前有很多成熟的緩存產(chǎn)品惭等,包括Redis,memcached办铡。這里以redis為例來分析下使用緩存實(shí)現(xiàn)分布式鎖的方案咕缎。關(guān)于Redis和memcached在網(wǎng)絡(luò)上有很多相關(guān)的文章珠十,并且也有一些成熟的框架及算法可以直接使用。
/**
* 嘗試獲取分布式鎖
* @param lockKey 鎖標(biāo)識key
* @param requestId 請求標(biāo)識
* @param expireTime 超時時間
* @return 是否獲取成功
*/
public boolean tryGetDistributedLock(String lockKey, String requestId, int expireTime){
//獲取jedis客戶端連接
Jedis conn = jedisPool.getResource();
String result = conn.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime);
// LOGGER.info("===加鎖結(jié)果result:" + result);
if(LOCK_SUCCESS.equals(result)){
return true;
}
return false;
}
以上凭豪,我們加鎖就一行代碼,jedis.set(String key, String value, String nxxx, String expx, int time)晒杈,這個set()方法一共有五個形參:
- 第一個參數(shù)為key嫂伞,我們使用key來當(dāng)鎖,因?yàn)閗ey是唯一的拯钻。
- 第二個參數(shù)為value帖努,我們傳的是requestId,很多童鞋可能不明白粪般,有key作為鎖不就夠了嗎拼余,為什么還要用到value?原因就是我們在上面講到可靠性時亩歹,分布式鎖要滿足第四個條件解鈴還須系鈴人匙监,通過給value賦值為requestId,我們就知道這把鎖是哪個請求加的了小作,在解鎖的時候就可以有依據(jù)亭姥。requestId可以使用UUID.randomUUID().toString()方法生成。
- 第三個參數(shù)為nxxx顾稀,這個參數(shù)我們填的是NX达罗,意思是SET IF NOT EXIST,即當(dāng)key不存在時静秆,我們進(jìn)行set操作粮揉;若key已經(jīng)存在,則不做任何操作抚笔。
- 第四個參數(shù)為expx扶认,這個參數(shù)我們傳的是PX,意思是我們要給這個key加一個過期的設(shè)置塔沃,具體時間由第五個參數(shù)決定蝠引。
- 第五個參數(shù)為time,與第四個參數(shù)相呼應(yīng)蛀柴,代表key的過期時間螃概。
總的來說,執(zhí)行上面的set()方法就只會導(dǎo)致兩種結(jié)果:
- 當(dāng)前沒有鎖(key不存在)鸽疾,那么就進(jìn)行加鎖操作吊洼,并對鎖設(shè)置個有效期,同時value表示加鎖的客戶端制肮。
- 已有鎖存在冒窍,不做任何操作递沪。
以上實(shí)現(xiàn)方式同樣存在幾個問題:
- 這把鎖沒有失效時間,一旦解鎖操作失敗综液,就會導(dǎo)致鎖記錄一直在redis中款慨,其他線程無法再獲得到鎖。
- 這把鎖只能是非阻塞的谬莹,無論成功還是失敗都直接返回檩奠。
- 這把鎖是非重入的,一個線程獲得鎖之后附帽,在釋放鎖之前埠戳,無法再次獲得該鎖,因?yàn)槭褂玫降膋ey在redis中已經(jīng)存在蕉扮。無法再執(zhí)行setNX操作整胃。
當(dāng)然,同樣有方式可以解決喳钟。
1. 沒有失效時間屁使?
jedisPool的setNX方法支持傳入失效時間,到達(dá)時間之后數(shù)據(jù)會自動刪除荚藻。
2. 非阻塞屋灌?
while重復(fù)執(zhí)行。
3. 非可重入应狱?
在一個線程獲取到鎖之后共郭,把當(dāng)前主機(jī)信息和線程信息保存起來,下次再獲取之前先檢查自己是不是當(dāng)前鎖的擁有者疾呻。
但是除嘹,失效時間我設(shè)置多長時間為好?如何設(shè)置的失效時間太短岸蜗,方法沒等執(zhí)行完尉咕,鎖就自動釋放了,那么就會產(chǎn)生并發(fā)問題璃岳。如果設(shè)置的時間太長年缎,其他獲取鎖的線程就可能要平白的多等一段時間。這個問題使用數(shù)據(jù)庫實(shí)現(xiàn)分布式鎖同樣存在铃慷。
總結(jié)
可以使用緩存來代替數(shù)據(jù)庫來實(shí)現(xiàn)分布式鎖单芜,這個可以提供更好的性能,同時犁柜,很多緩存服務(wù)都是集群部署的洲鸠,可以避免單點(diǎn)問題。并且很多緩存服務(wù)都提供了可以用來實(shí)現(xiàn)分布式鎖的方法, redis的setNX方法等扒腕。并且绢淀,這些緩存服務(wù)也都提供了對數(shù)據(jù)的過期自動刪除的支持,可以直接設(shè)置超時時間來控制鎖的釋放瘾腰。
- 使用緩存實(shí)現(xiàn)分布式鎖的優(yōu)點(diǎn)
- 性能好皆的,實(shí)現(xiàn)起來較為方便。
- 使用緩存實(shí)現(xiàn)分布式鎖的缺點(diǎn)
- 通過超時時間來控制鎖的失效時間并不是十分的靠譜居灯。
基于Zookeeper實(shí)現(xiàn)分布式鎖
Zookeeper的一些重要特性:
1. 同一時刻多臺機(jī)器創(chuàng)建同一個節(jié)點(diǎn)祭务,只有一個會爭搶成功。
利用這個特性可以做分布式鎖怪嫌。
2. 臨時節(jié)點(diǎn)的生命周期與會話一致,會話關(guān)閉則臨時節(jié)點(diǎn)刪除柳沙。
這個特性經(jīng)常用來做心跳岩灭,動態(tài)監(jiān)控,負(fù)載等動作赂鲤。
3. 順序節(jié)點(diǎn)保證節(jié)點(diǎn)名全局唯一噪径。
這個特性可以用來生成分布式環(huán)境下的全局自增長id。
既然Zookeeper天然具有以上一些特性数初,那么我們就可以基于zookeeper臨時有序節(jié)點(diǎn)的這個特性來實(shí)現(xiàn)分布式鎖找爱。
大致思想:每個客戶端對某個方法加鎖時,在zookeeper上的與該方法對應(yīng)的指定節(jié)點(diǎn)的目錄下泡孩,生成一個唯一的瞬時有序節(jié)點(diǎn)车摄。 判斷是否獲取鎖的方式很簡單,只需要判斷有序節(jié)點(diǎn)中序號最小的一個仑鸥。 當(dāng)釋放鎖的時候吮播,只需將這個瞬時節(jié)點(diǎn)刪除即可。同時眼俊,其可以避免服務(wù)宕機(jī)導(dǎo)致的鎖無法釋放意狠,而產(chǎn)生的死鎖問題。
來看下Zookeeper能不能解決前面提到的問題疮胖。
1. 鎖無法釋放环戈?
使用Zookeeper可以有效的解決鎖無法釋放的問題,因?yàn)樵趧?chuàng)建鎖的時候澎灸,客戶端會在ZK中創(chuàng)建一個臨時節(jié)點(diǎn)院塞,一旦客戶端獲取到鎖之后突然掛掉(Session連接斷開),那么這個臨時節(jié)點(diǎn)就會自動刪除掉击孩。其他客戶端就可以再次獲得鎖迫悠。
2. 非阻塞鎖?
使用Zookeeper可以實(shí)現(xiàn)阻塞的鎖巩梢,客戶端可以通過在ZK中創(chuàng)建順序節(jié)點(diǎn)创泄,并且在節(jié)點(diǎn)上綁定監(jiān)聽器艺玲,一旦節(jié)點(diǎn)有變化,Zookeeper會通知客戶端鞠抑,客戶端可以檢查自己創(chuàng)建的節(jié)點(diǎn)是不是當(dāng)前所有節(jié)點(diǎn)中序號最小的饭聚,如果是,那么自己就獲取到鎖搁拙,便可以執(zhí)行業(yè)務(wù)邏輯了秒梳。
3. 不可重入?
使用Zookeeper也可以有效的解決不可重入的問題箕速,客戶端在創(chuàng)建節(jié)點(diǎn)的時候酪碘,把當(dāng)前客戶端的主機(jī)信息和線程信息直接寫入到節(jié)點(diǎn)中,下次想要獲取鎖的時候和當(dāng)前最小的節(jié)點(diǎn)中的數(shù)據(jù)比對一下就可以了盐茎。如果和自己的信息一樣兴垦,那么自己直接獲取到鎖,如果不一樣就再創(chuàng)建一個臨時的順序節(jié)點(diǎn)字柠,參與排隊(duì)探越。
4. 單點(diǎn)問題?
使用Zookeeper可以有效的解決單點(diǎn)問題窑业,ZK是集群部署的钦幔,只要集群中有半數(shù)以上的機(jī)器存活,就可以對外提供服務(wù)常柄。
可以直接使用zookeeper第三方庫Curator客戶端鲤氢,這個客戶端中封裝了一個可重入的鎖服務(wù)。
/**
* 加鎖
* @param maxWaitTime 最大等待超時時間
* @param waitUnit 最大等待超時時間單位
* @return true-成功拐纱,false-失敗
*/
public boolean lock(long maxWaitTime, TimeUnit waitUnit){
if(null == lock){
return false;
}
try {
//加鎖
return lock.acquire(maxWaitTime, waitUnit);
} catch (Exception e) {
//加鎖失敗
LOGGER.error("加鎖失斖臁: " + e.getMessage());
}
return false;
}
/**
* 加鎖
* @return
*/
public boolean lock(){
return lock(LOCK_MAX_WAIT_TIMITOUT, TimeUnit.MILLISECONDS);
}
/**
* 解鎖
* @return
*/
public void unLock(){
if(null == lock){
return;
}
try {
lock.release();
} catch (Exception e) {
//解鎖失敗
LOGGER.error("解鎖失敗: " + e.getMessage());
}
}
Curator提供的InterProcessMutex是分布式鎖的實(shí)現(xiàn)秸架。
acquire方法用戶獲取鎖揍庄,release方法用于釋放鎖。
使用ZK實(shí)現(xiàn)的分布式鎖好像完全符合了本文開頭我們對一個分布式鎖的所有期望东抹。但是蚂子,其實(shí)并不是,Zookeeper實(shí)現(xiàn)的分布式鎖其實(shí)存在一個缺點(diǎn)缭黔,那就是性能上可能并沒有緩存服務(wù)那么高食茎。因?yàn)槊看卧趧?chuàng)建鎖和釋放鎖的過程中,都要動態(tài)創(chuàng)建馏谨、銷毀瞬時節(jié)點(diǎn)來實(shí)現(xiàn)鎖功能别渔。ZK中創(chuàng)建和刪除節(jié)點(diǎn)只能通過Leader服務(wù)器來執(zhí)行,然后將數(shù)據(jù)同不到所有的Follower機(jī)器上。
其實(shí)哎媚,使用Zookeeper也有可能帶來并發(fā)問題喇伯,只是并不常見而已〔τ耄考慮這樣的情況稻据,由于網(wǎng)絡(luò)抖動,客戶端可ZK集群的session連接斷了买喧,那么zk以為客戶端掛了捻悯,就會刪除臨時節(jié)點(diǎn),這時候其他客戶端就可以獲取到分布式鎖了淤毛。就可能產(chǎn)生并發(fā)問題今缚。這個問題不常見是因?yàn)閦k有重試機(jī)制,一旦zk集群檢測不到客戶端的心跳低淡,就會重試荚斯,Curator客戶端支持多種重試策略。多次重試之后還不行的話才會刪除臨時節(jié)點(diǎn)查牌。(所以,選擇一個合適的重試策略也比較重要滥壕,要在鎖的粒度和并發(fā)之間找一個平衡纸颜。)
總結(jié)
使用Zookeeper實(shí)現(xiàn)分布式鎖的優(yōu)點(diǎn)
有效的解決單點(diǎn)問題,不可重入問題绎橘,非阻塞問題以及鎖無法釋放的問題胁孙。實(shí)現(xiàn)起來較為簡單。使用Zookeeper實(shí)現(xiàn)分布式鎖的缺點(diǎn)
性能上不如使用緩存實(shí)現(xiàn)分布式鎖称鳞。 需要對ZK的原理有所了解涮较。
三種方案的比較
上面幾種方式,哪種方式都無法做到完美冈止。就像CAP一樣狂票,在復(fù)雜性、可靠性熙暴、性能等方面無法同時滿足闺属,所以,根據(jù)不同的應(yīng)用場景選擇最適合自己的才是王道周霉。
1. 從理解的難易程度角度(從低到高)
數(shù)據(jù)庫 > 緩存 > Zookeeper
2. 從實(shí)現(xiàn)的復(fù)雜性角度(從低到高)
Zookeeper >= 緩存 > 數(shù)據(jù)庫
3. 從性能角度(從高到低)
緩存 > Zookeeper >= 數(shù)據(jù)庫
4. 從可靠性角度(從高到低)
Zookeeper > 緩存 > 數(shù)據(jù)庫
Demo實(shí)例演示
代碼已上傳至github:git@github.com:hu1991die/distribute-tool-integretion.git
原文參考: