如未做特殊說明贝椿,本文均為原創(chuàng),轉(zhuǎn)載請注明出處陷谱。
[TOC]
前言
為什么要使用分布式鎖呢烙博?
在Nginx
實現(xiàn)負載均衡服務器集群時會產(chǎn)生很多問題,在提高并發(fā)的同時叭首,服務器也會產(chǎn)生非常多的問題例如习勤,這些問題應該一一的考慮到。
- 分布式Session一致性
- 分布式全局ID生成方案
- 分布式事務解決方案
- 分布式任務調(diào)度平臺
- 分布式配置中心
- 分布式鎖多種實現(xiàn)方案
- 分布式日志收集系統(tǒng)
- 各種網(wǎng)站跨域請求解決方案
- 高并發(fā)下服務降級與限流實戰(zhàn)
- ……
本次就先以分布式鎖來探討一下分布式場景下的使用與注意事項焙格,和為什么要使用分布式鎖图毕。
為什么要使用分布式鎖
分布鎖產(chǎn)生的原因
? 因為服務器使用了集群方案。
那么在單臺服務器上如何生成訂單號(保證唯一)
? UUID眷唉、時間戳予颤、redis等囤官。。蛤虐。
其實有一種取巧的解決方案党饮,可以適當?shù)谋苊馔蝗坏母卟l(fā)下的服務宕機(或反應慢)的尷尬場景。
--可以使用redis
提前生成150W訂單號(假設(shè)是在下單場景下)驳庭。
為什么要用redis
去生成訂單號刑顺?
? 提前生成好150W訂單號,存放在redis
中饲常,當客戶端下單之后蹲堂,直接到redis
中取對應的訂單號即可,因為redis
本身是單線程的贝淤,如果redis
還只剩下50W個訂單號的時候柒竞,在繼續(xù)生產(chǎn)100W個訂單號。(這種技巧視業(yè)務場景而定播聪,一般我們在面對需求或者發(fā)生的問題時朽基,可以從根源下手,這種情況明顯就是針對在高并發(fā)場景下能一定程度的提高程序的響應速度离陶。提高了整個程序的健壯性稼虎。)
那么如果在集群環(huán)境下使用UUID
的方式安全嘛?
? 答案是肯定的枕磁,肯定時不安全的渡蜻,大概率是會出現(xiàn)重復的ID
的。所以這個時候就提出了分布式鎖计济。
? 可想而知茸苇,在多線程下我們可以簡單的使用Lock
或者synchronized
來實現(xiàn)安全性,但是在分布式場景下沦寂,這些就顯得力不從心了学密。因為這個時候在微服務場景下,很有可能同一個服務分別安裝在不同的服務器上传藏,這樣的話腻暮,傳統(tǒng)的加鎖方式根本不可能辦證數(shù)據(jù)的最終一致性。所以分布式鎖就誕生了毯侦。
? 在很多場景中哭靖,我們?yōu)榱吮WC數(shù)據(jù)的最終一致性,需要很多的技術(shù)方案來支持侈离,比如分布式事務试幽、分布式鎖等。有的時候卦碾,我們需要保證一個方法在同
? 一時間內(nèi)只能被同一個線程執(zhí)行铺坞。在單機環(huán)境中起宽,Java中其實提供了很多并發(fā)處理相關(guān)的API,但是這些API在分布式場景中就無能為力了济榨。也就是說單
? 純的Java Api并不能提供分布式鎖的能力坯沪。所以針對分布式鎖的實現(xiàn)目前有多種方案:
? 分布式鎖一般有三種實現(xiàn)方式:1. 數(shù)據(jù)庫鎖;2. 基于redis
的分布式鎖擒滑;3. 基于Zookeeper
的分布式鎖腐晾。
分布式鎖應該是怎么樣的
- 互斥性 可以保證在分布式部署的應用集群中,同一個方法在同一時間只能被一臺機器上的一個線程執(zhí)行橘忱。
- 這把鎖要是一把可重入鎖(避免死鎖)
- 不會發(fā)生死鎖:有一個客戶端在持有鎖的過程中崩潰而沒有解鎖赴魁,也能保證其他客戶端能夠加鎖
- 這把鎖最好是一把阻塞鎖(根據(jù)業(yè)務需求考慮要不要這條)
- 有高可用的獲取鎖和釋放鎖功能
- 獲取鎖和釋放鎖的性能要好
數(shù)據(jù)庫鎖
基于數(shù)據(jù)庫表
? 要實現(xiàn)分布式鎖,最簡單的方式可能就是直接創(chuàng)建一張鎖表钝诚,然后通過操作該表中的數(shù)據(jù)來實現(xiàn)了。
當我們要鎖住某個方法或資源時榄棵,我們就在該表中增加一條記錄凝颇,想要釋放鎖的時候就刪除這條記錄。
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='鎖定中的方法';
當我們想要鎖住某個方法時拧略,執(zhí)行以下SQL:
insert into methodLock(method_name,desc) values (‘method_name’,‘desc’)
因為我們對method_name做了唯一性約束,這里如果有多個請求同時提交到數(shù)據(jù)庫的話瘪弓,數(shù)據(jù)庫會保證只有一個操作可以成功垫蛆,那么我們就可以認為
操作成功的那個線程獲得了該方法的鎖,可以執(zhí)行方法體內(nèi)容腺怯。
當方法執(zhí)行完畢之后袱饭,想要釋放鎖的話,需要執(zhí)行以下Sql:
delete from methodLock where method_name ='method_name'
上面這種簡單的實現(xiàn)有以下幾個問題:
1呛占、這把鎖強依賴數(shù)據(jù)庫的可用性虑乖,數(shù)據(jù)庫是一個單點,一旦數(shù)據(jù)庫掛掉晾虑,會導致業(yè)務系統(tǒng)不可用疹味。
2、這把鎖沒有失效時間帜篇,一旦解鎖操作失敗糙捺,就會導致鎖記錄一直在數(shù)據(jù)庫中,其他線程無法再獲得到鎖笙隙。
3洪灯、這把鎖只能是非阻塞的,因為數(shù)據(jù)的insert操作逃沿,一旦插入失敗就會直接報錯婴渡。沒有獲得鎖的線程并不會進入排隊隊列幻锁,要想再次獲得鎖就要再次觸發(fā)獲得鎖操作。
4边臼、這把鎖是非重入的哄尔,同一個線程在沒有釋放鎖之前無法再次獲得該鎖。因為數(shù)據(jù)中數(shù)據(jù)已經(jīng)存在了柠并。
當然岭接,我們也可以有其他方式解決上面的問題。
- 數(shù)據(jù)庫是單點臼予?搞兩個數(shù)據(jù)庫鸣戴,數(shù)據(jù)之前雙向同步。一旦掛掉快速切換到備庫上粘拾。
- 沒有失效時間窄锅?只要做一個定時任務,每隔一定時間把數(shù)據(jù)庫中的超時數(shù)據(jù)清理一遍缰雇。
- 非阻塞的入偷?搞一個while循環(huán),直到insert成功再返回成功械哟。
- 非重入的疏之?在數(shù)據(jù)庫表中加個字段,記錄當前獲得鎖的機器的主機信息和線程信息暇咆,那么下次再獲取鎖的時候先查詢數(shù)據(jù)庫锋爪,如果當前機器的主機信息和線程信息在數(shù)據(jù)庫可以查到的話,直接把鎖分配給他就可以了爸业。
這樣你是不是覺得離我們分布式微服務宗旨越來越遠了呢其骄???
基于數(shù)據(jù)庫的排它鎖
除了可以通過增刪操作數(shù)據(jù)表中的記錄以外,其實還可以借助數(shù)據(jù)庫中自帶的鎖來實現(xiàn)分布式的鎖沃呢。
我們還用剛剛創(chuàng)建的那張數(shù)據(jù)庫表年栓。可以通過數(shù)據(jù)庫的排他鎖來實現(xiàn)分布式鎖薄霜。
在查詢語句后面增加for update某抓,數(shù)據(jù)庫會在查詢過程中給數(shù)據(jù)庫表增加排他鎖。當某條記錄被加上排他鎖之后惰瓜,其他線程無法再在該行記錄上增加排他鎖否副。
我們可以認為獲得排它鎖的線程即可獲得分布式鎖,當獲取到鎖之后崎坊,可以執(zhí)行方法的業(yè)務邏輯备禀,執(zhí)行完方法之后,再通過以下方法解鎖:
public void unlock(){
connection.commit();
}
通過connection.commit()操作來釋放鎖。
這種方法可以有效的解決上面提到的無法釋放鎖和阻塞鎖的問題曲尸。
- 阻塞鎖赋续? for update語句會在執(zhí)行成功后立即返回,在執(zhí)行失敗時一直處于阻塞狀態(tài)另患,直到成功纽乱。
- 鎖定之后服務宕機,無法釋放昆箕?使用這種方式鸦列,服務宕機之后數(shù)據(jù)庫會自己把鎖釋放掉。
但是還是無法直接解決數(shù)據(jù)庫單點和可重入問題鹏倘。
總結(jié):
總結(jié)一下使用數(shù)據(jù)庫來實現(xiàn)分布式鎖的方式薯嗤,這兩種方式都是依賴數(shù)據(jù)庫的一張表,一種是通過表中的記錄的存在情況確定當前是否有鎖存在纤泵,另外一種是通過數(shù)據(jù)庫的排他鎖來實現(xiàn)分布式鎖骆姐。
**數(shù)據(jù)庫實現(xiàn)分布式鎖的優(yōu)點: **直接借助數(shù)據(jù)庫,容易理解夕吻。
**數(shù)據(jù)庫實現(xiàn)分布式鎖的缺點: **會有各種各樣的問題诲锹,在解決問題的過程中會使整個方案變得越來越復雜。
操作數(shù)據(jù)庫需要一定的開銷涉馅,性能問題需要考慮。
樂觀鎖
樂觀鎖假設(shè)認為數(shù)據(jù)一般情況下不會造成沖突黄虱,只有在進行數(shù)據(jù)的提交更新時稚矿,才會檢測數(shù)據(jù)的沖突情況,如果發(fā)現(xiàn)沖突了捻浦,則返回錯誤信息
實現(xiàn)方式:
時間戳(timestamp)記錄機制實現(xiàn):給數(shù)據(jù)庫表增加一個時間戳字段類型的字段晤揣,當讀取數(shù)據(jù)時,將timestamp字段的值一同讀出朱灿,數(shù)據(jù)每更新一次昧识,timestamp也同步更新。當對數(shù)據(jù)做提交更新操作時盗扒,檢查當前數(shù)據(jù)庫中數(shù)據(jù)的時間戳和自己更新前取到的時間戳進行對比跪楞,若相等,則更新侣灶,否則認為是失效數(shù)據(jù)甸祭。
若出現(xiàn)更新沖突,則需要上層邏輯修改褥影,啟動重試機制
同樣也可以使用version的方式池户。
性能對比
(1) 悲觀鎖實現(xiàn)方式是獨占數(shù)據(jù),其它線程需要等待,不會出現(xiàn)修改的沖突校焦,能夠保證數(shù)據(jù)的一致性赊抖,但是依賴數(shù)據(jù)庫的實現(xiàn),且在線程較多時出現(xiàn)等待造成效率降低的問題寨典。一般情況下氛雪,對于數(shù)據(jù)很敏感且讀取頻率較低的場景,可以采用悲觀鎖的方式
(2) 樂觀鎖可以多線程同時讀取數(shù)據(jù)凝赛,若出現(xiàn)沖突注暗,也可以依賴上層邏輯修改,能夠保證高并發(fā)下的讀取墓猎,適用于讀取頻率很高而修改頻率較少的場景
(3) 由于庫存回寫數(shù)據(jù)屬于敏感數(shù)據(jù)且讀取頻率適中捆昏,所以建議使用悲觀鎖優(yōu)化
基于redis
的分布式鎖
相比較于基于數(shù)據(jù)庫實現(xiàn)分布式鎖的方案來說,基于緩存來實現(xiàn)在性能方面會表現(xiàn)的更好一點毙沾。而且很多緩存是可以集群部署的骗卜,可以解決單點問題。
首先左胞,為了確保分布式鎖可用寇仓,我們至少要確保鎖的實現(xiàn)同時滿足以下四個條件:
- 互斥性。在任意時刻烤宙,只有一個客戶端能持有鎖遍烦。
- 不會發(fā)生死鎖。即使有一個客戶端在持有鎖的期間崩潰而沒有主動解鎖躺枕,也能保證后續(xù)其他客戶端能加鎖服猪。
- 具有容錯性。只要大部分的
redis
節(jié)點正常運行拐云,客戶端就可以加鎖和解鎖罢猪。 - 解鈴還須系鈴人。加鎖和解鎖必須是同一個客戶端叉瘩,客戶端自己不能把別人加的鎖給解了膳帕。
可以看到,我們加鎖就一行代碼:jedis.set(String key, String value, String nxxx, String expx, int time)薇缅,這個set()方法一共有五個形參:
- 第一個為key危彩,我們使用key來當鎖,因為key是唯一的捅暴。
- 第二個為value恬砂,我們傳的是requestId,很多童鞋可能不明白蓬痒,有key作為鎖不就夠了嗎泻骤,為什么還要用到value漆羔?原因就是我們在上面講到可靠性時,分布式鎖要滿足第四個條件解鈴還須系鈴人狱掂,通過給value賦值為requestId演痒,我們就知道這把鎖是哪個請求加的了,在解鎖的時候就可以有依據(jù)趋惨。requestId可以使用
UUID.randomUUID().toString()
方法生成鸟顺。 - 第三個為nxxx,這個參數(shù)我們填的是NX器虾,意思是SET IF NOT EXIST讯嫂,即當key不存在時,我們進行set操作兆沙;若key已經(jīng)存在欧芽,則不做任何操作;
- 第四個為expx葛圃,這個參數(shù)我們傳的是PX千扔,意思是我們要給這個key加一個過期的設(shè)置,具體時間由第五個參數(shù)決定库正。
- 第五個為time曲楚,與第四個參數(shù)相呼應,代表key的過期時間褥符。
總的來說龙誊,執(zhí)行上面的set()方法就只會導致兩種結(jié)果:1. 當前沒有鎖(key不存在),那么就進行加鎖操作喷楣,并對鎖設(shè)置個有效期载迄,同時value表示加鎖的客戶端。2. 已有鎖存在抡蛙,不做任何操作。
心細的童鞋就會發(fā)現(xiàn)了魂迄,我們的加鎖代碼滿足我們可靠性里描述的三個條件粗截。首先,set()加入了NX參數(shù)捣炬,可以保證如果已有key存在熊昌,則函數(shù)不會調(diào)用成功,也就是只有一個客戶端能持有鎖湿酸,滿足互斥性婿屹。其次,由于我們對鎖設(shè)置了過期時間推溃,即使鎖的持有者后續(xù)發(fā)生崩潰而沒有解鎖昂利,鎖也會因為到了過期時間而自動解鎖(即key被刪除),不會發(fā)生死鎖。最后蜂奸,因為我們將value賦值為requestId犁苏,代表加鎖的客戶端請求標識,那么在客戶端在解鎖的時候就可以進行校驗是否是同一個客戶端扩所。由于我們只考慮redis
單機部署的場景围详,所以容錯性我們暫不考慮。
錯誤實例:
使用jedis.setnx()
和jedis.expire()
組合實現(xiàn)加鎖
public static void wrongGetLock1(Jedis jedis, String lockKey, String requestId, int expireTime) {
Long result = jedis.setnx(lockKey, requestId);
if (result == 1) {
// 若在這里程序突然崩潰祖屏,則無法設(shè)置過期時間助赞,將發(fā)生死鎖 jedis.expire(lockKey, expireTime);
}
}
setnx()方法作用就是SET IF NOT EXIST,expire()方法就是給鎖加一個過期時間袁勺。乍一看好像和前面的set()方法結(jié)果一樣雹食,然而由于這是兩條redis
命令,不具有原子性魁兼,如果程序在執(zhí)行完setnx()之后突然崩潰婉徘,導致鎖沒有設(shè)置過期時間。那么將會發(fā)生死鎖咐汞。網(wǎng)上之所以有人這樣實現(xiàn)盖呼,是因為低版本的jedis并不支持多參數(shù)的set()方法。
解鎖:
首先獲取鎖對應的value值化撕,檢查是否與requestId相等几晤,如果相等則刪除鎖(解鎖)
總結(jié):
可以使用緩存來代替數(shù)據(jù)庫來實現(xiàn)分布式鎖,這個可以提供更好的性能植阴,同時蟹瘾,很多緩存服務都是集群部署的,可以避免單點問題掠手。并且很多緩存服務都提供了可以用來實現(xiàn)分布式鎖的方法憾朴,比如redis
的setnx方法等。并且喷鸽,這些緩存服務也都提供了對數(shù)據(jù)的過期自動刪除的支持众雷,可以直接設(shè)置超時時間來控制鎖的釋放。
使用緩存實現(xiàn)分布式鎖的優(yōu)點
性能好做祝,實現(xiàn)起來較為方便砾省。
使用緩存實現(xiàn)分布式鎖的缺點
通過超時時間來控制鎖的失效時間并不是十分的靠譜。
基于Zookeeper
實現(xiàn)分布式鎖
? 基于Zookeeper
臨時有序節(jié)點可以實現(xiàn)的分布式鎖混槐。大致思想即為:每個客戶端對某個方法加鎖時编兄,在Zookeeper
上的與該方法對應的指定節(jié)點的目錄下,生成一個唯一的瞬時有序節(jié)點声登。 判斷是否獲取鎖的方式很簡單狠鸳,只需要判斷有序節(jié)點中序號最小的一個揣苏。 當釋放鎖的時候,只需將這個瞬時節(jié)點刪除即可碰煌。同時舒岸,其可以避免服務宕機導致的鎖無法釋放,而產(chǎn)生的死鎖問題。
? 原理:zookeeper
是利用了自身臨時節(jié)點且唯一性的優(yōu)勢,可以很好的實現(xiàn)分布式鎖機制愚隧。當多個服務器同時訪問同一個需要安全同步對象時,這個時候可以使用zookeeper
實現(xiàn)分布式鎖洪乍,在多個服務器訪問的情況下有一個前提就是需要獲取zookeeper
下的節(jié)點對象,但是創(chuàng)建該節(jié)點只能是唯一的且為臨時的(當訪問結(jié)束夜焦,斷開連接時會被自動釋放(刪除))壳澳。所以在多個服務器同時訪問的情況下,只能有一個服務器創(chuàng)建成功該臨時節(jié)點茫经,這樣一來就可以解決在分布式場景下巷波,數(shù)據(jù)被多臺服務器訪問的一致性問題。
代碼如下:
首先定義一個頂層接口Lock
/**
* zookeeper 分布式鎖 頂層對象
*
* @author by Assume
* @date 2019/3/30 17:04
*/
public interface Lock {
/**
* 獲取鎖
*/
public void getLock();
/**
* 解鎖
*/
void unLock();
}
定義實現(xiàn)Lock
接口的抽象類ZookeeperAbstractLock
/**
* 獲取鎖 抽象類卸伞,利用裝飾著模式抹镊,讓子類具體實現(xiàn)。
*
* @author by Assume
* @date 2019/3/30 17:06
*/
public abstract class ZookeeperAbstractLock implements Lock {
private static final String ADDR = "127.0.0.1:2181";
// 創(chuàng)建zk連接
protected ZkClient zkClient = new ZkClient(ADDR);
// 定義zookeeper 需要創(chuàng)建的節(jié)點
protected static final String PATH = "/lock";
@Override
public void getLock() {
if (tryLock()) {
System.out.println("-----獲取鎖成功");
} else {
// 等待
waitLock();
// 重新獲取鎖資源
getLock();
}
}
// 等待
abstract void waitLock();
// 重新獲取鎖資源
abstract boolean tryLock();
/**
* 釋放鎖
*/
@Override
public void unLock() {
if (zkClient != null) {
zkClient.close();
System.out.println("---釋放資源成功");
}
}
}
使用裝飾者模式來具體實現(xiàn)該類的抽象方法荤傲,ZookeeperDistrbuteLock
/**
* 使用裝飾著模式垮耳,子類實現(xiàn)未完成的接口
*
* @author by Assume
* @date 2019/3/30 17:12
*/
public class ZookeeperDistrbuteLock extends ZookeeperAbstractLock {
// 定義信號量,實現(xiàn)節(jié)點動作的監(jiān)聽 等待與喚醒
private CountDownLatch countDownLatch = null;
@Override
boolean tryLock() {
try {
// 創(chuàng)建臨時節(jié)點
zkClient.createEphemeral(PATH);
return true;
// 如果該節(jié)點已經(jīng)存在遂黍,默認情況下會報錯终佛,則證明該節(jié)點已被加鎖,需要等待
} catch (Exception e) {
// 節(jié)點已經(jīng)被創(chuàng)建
return false;
}
}
@Override
void waitLock() {
// 等待雾家,需要監(jiān)聽節(jié)點的變化情況,接聽節(jié)點被刪除
IZkDataListener iZkDataListener = new IZkDataListener() {
@Override
public void handleDataChange(String dataPath, Object data) throws Exception {}
@Override
public void handleDataDeleted(String dataPath) throws Exception {
// 節(jié)點被刪除铃彰,喚醒等待的線程
if (countDownLatch != null) {
countDownLatch.countDown();
}
}
};
// 注冊事件
zkClient.subscribeDataChanges(PATH, iZkDataListener);
// 判斷該節(jié)點是否存在
if (zkClient.exists(PATH)) {
countDownLatch = new CountDownLatch(1);
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 刪除事件
zkClient.unsubscribeDataChanges(PATH, iZkDataListener);
}
}
具體加鎖小試牛刀??
/**
* @author by Assume
* @date 2019/3/30 17:25
*/
public class OrderService implements Runnable {
private Lock lock = new ZookeeperDistrbuteLock();
@Override
public void run() {
getNum();
}
private void getNum() {
try {
// 使用zookeeper 加鎖
lock.getLock();
System.out.println("生成訂單:" + Thread.currentThread().getName() + UUID.randomUUID());
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unLock();
}
}
public static void main(String[] args) {
System.out.println("####生成唯一訂單號###");
for (int i = 0; i < 100; i++) {
// new Thread(() -> new OrderService()).start();
new Thread(new OrderService()).start();
}
}
}
來看下Zookeeper
能不能解決前面提到的問題。
鎖無法釋放芯咧?使用
Zookeeper
可以有效的解決鎖無法釋放的問題豌研,因為在創(chuàng)建鎖的時候,客戶端會在ZK中創(chuàng)建一個臨時節(jié)點唬党,一旦客戶端獲取到鎖之后突然掛掉(Session連接斷開),那么這個臨時節(jié)點就會自動刪除掉鬼佣。其他客戶端就可以再次獲得鎖驶拱。(但是這張情況下,會產(chǎn)生一點的延時晶衷。)非阻塞鎖蓝纲?使用
Zookeeper
可以實現(xiàn)阻塞的鎖阴孟,客戶端可以通過在ZK中創(chuàng)建順序節(jié)點,并且在節(jié)點上綁定監(jiān)聽器税迷,一旦節(jié)點有變化永丝,Zookeeper
會通知客戶端,客戶端可以檢查自己創(chuàng)建的節(jié)點是不是當前所有節(jié)點中序號最小的箭养,如果是慕嚷,那么自己就獲取到鎖,便可以執(zhí)行業(yè)務邏輯了毕泌。不可重入喝检?使用
Zookeeper
也可以有效的解決不可重入的問題,客戶端在創(chuàng)建節(jié)點的時候撼泛,把當前客戶端的主機信息和線程信息直接寫入到節(jié)點中挠说,下次想要獲取鎖的時候和當前最小的節(jié)點中的數(shù)據(jù)比對一下就可以了。如果和自己的信息一樣愿题,那么自己直接獲取到鎖损俭,如果不一樣就再創(chuàng)建一個臨時的順序節(jié)點,參與排隊潘酗。單點問題杆兵?使用
Zookeeper
可以有效的解決單點問題,ZK是集群部署的崎脉,只要集群中有半數(shù)以上的機器存活拧咳,就可以對外提供服務∏糇疲可以直接使用Zookeeper
第三方庫Curator
客戶端骆膝,這個客戶端中封裝了一個可重入的鎖服務。
Zookeeper
實現(xiàn)的分布式鎖其實存在一個缺點灶体,那就是性能上可能并沒有緩存服務那么高阅签。
因為每次在創(chuàng)建鎖和釋放鎖的過程中,都要動態(tài)創(chuàng)建蝎抽、銷毀瞬時節(jié)點來實現(xiàn)鎖功能政钟。ZK中創(chuàng)建和刪除節(jié)點只能通過Leader
服務器來執(zhí)行,然后將數(shù)據(jù)同不到所有的Follower
機器上樟结。
**使用Zookeeper
實現(xiàn)分布式鎖的優(yōu)點: **有效的解決單點問題养交,不可重入問題,非阻塞問題以及鎖無法釋放的問題瓢宦。實現(xiàn)起來較為簡單碎连。
**使用Zookeeper
實現(xiàn)分布式鎖的缺點 : **性能上不如使用緩存實現(xiàn)分布式鎖。 需要對ZK的原理有所了解驮履。