柚子快報邀請碼778899分享:Zookeeper
柚子快報邀請碼778899分享:Zookeeper
Zookeeper是一個分布式協(xié)調(diào)服務(wù),用于管理和協(xié)調(diào)分布式應(yīng)用程序的組件。它提供了集中式的服務(wù),用于維護(hù)配置信息、命名、分布式同步和組服務(wù)。Zookeeper可以幫助開發(fā)人員簡化分布式應(yīng)用的設(shè)計和實(shí)現(xiàn)。
Zookeeper的核心概念
節(jié)點(diǎn)(ZNode):
Zookeeper的數(shù)據(jù)模型類似于文件系統(tǒng),由一棵層次化的節(jié)點(diǎn)樹組成。每個節(jié)點(diǎn)稱為ZNode,可以存儲數(shù)據(jù)和子節(jié)點(diǎn)。ZNode有兩種類型:臨時節(jié)點(diǎn)(Ephemeral)和持久節(jié)點(diǎn)(Persistent)。
臨時節(jié)點(diǎn):會話結(jié)束時自動刪除。持久節(jié)點(diǎn):需要明確刪除操作才能刪除。 會話(Session):
客戶端與Zookeeper服務(wù)器之間的連接稱為會話。會話是有超時時間的,客戶端需要定期發(fā)送心跳來維持會話。 版本(Version):
每個ZNode都有版本信息,包括數(shù)據(jù)版本(dataVersion)、子節(jié)點(diǎn)版本(cversion)和ACL版本(aversion)。版本信息在更新時自動遞增,用于實(shí)現(xiàn)樂觀鎖機(jī)制。 監(jiān)視(Watchers):
客戶端可以在ZNode上設(shè)置監(jiān)視器,當(dāng)ZNode發(fā)生變化時,客戶端會收到通知。監(jiān)視是一次性的,需要重新設(shè)置。
Zookeeper的工作原理
集群架構(gòu):
Zookeeper通常部署為集群,稱為Zookeeper Ensemble。集群中的每個服務(wù)器稱為一個節(jié)點(diǎn),節(jié)點(diǎn)分為領(lǐng)導(dǎo)者(Leader)和跟隨者(Follower)。Leader負(fù)責(zé)處理寫請求,并同步到Followers,F(xiàn)ollowers負(fù)責(zé)處理讀請求。 一致性協(xié)議:
Zookeeper使用Zab協(xié)議(Zookeeper Atomic Broadcast)來保證集群的一致性。Zab協(xié)議類似于Paxos協(xié)議,確保在Leader和Follower之間的狀態(tài)同步和數(shù)據(jù)一致性。 數(shù)據(jù)復(fù)制:
Zookeeper的每個節(jié)點(diǎn)都維護(hù)一個內(nèi)存中的數(shù)據(jù)副本,通過事務(wù)日志和快照機(jī)制保證數(shù)據(jù)的持久性。當(dāng)Leader接收到寫請求時,它會生成事務(wù)ID(ZXID),并將請求廣播給所有Followers進(jìn)行復(fù)制。
Zookeeper的應(yīng)用場景
配置管理:
集中式存儲和管理配置信息,確保分布式系統(tǒng)中的各個組件使用一致的配置信息。 命名服務(wù):
提供分布式命名服務(wù),將資源名稱映射到物理地址,實(shí)現(xiàn)動態(tài)服務(wù)發(fā)現(xiàn)。 分布式鎖:
通過創(chuàng)建臨時節(jié)點(diǎn),實(shí)現(xiàn)分布式鎖,確保在分布式環(huán)境中只有一個客戶端可以訪問共享資源。 集群管理:
管理集群中的節(jié)點(diǎn)狀態(tài),監(jiān)控節(jié)點(diǎn)的加入和離開,實(shí)現(xiàn)高可用性和負(fù)載均衡。 Leader選舉:
在分布式系統(tǒng)中,通過Zookeeper實(shí)現(xiàn)Leader選舉,確保系統(tǒng)中只有一個主節(jié)點(diǎn)進(jìn)行操作。
在Spring Boot中集成Zookeeper
在Spring Boot中,可以使用Spring Cloud Zookeeper來簡化與Zookeeper的集成。
添加依賴:
配置Zookeeper連接信息: 在application.yml文件中配置Zookeeper服務(wù)器地址: spring:
cloud:
zookeeper:
connect-string: localhost:2181
使用Zookeeper進(jìn)行配置管理: 通過注解@EnableZookeeperConfig啟用Zookeeper配置管理: @SpringBootApplication
@EnableZookeeperConfig
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}
使用Zookeeper進(jìn)行服務(wù)注冊和發(fā)現(xiàn): 通過注解@EnableDiscoveryClient啟用服務(wù)注冊和發(fā)現(xiàn): @SpringBootApplication
@EnableDiscoveryClient
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}
Zookeeper是一個強(qiáng)大的分布式協(xié)調(diào)服務(wù),廣泛應(yīng)用于分布式系統(tǒng)的配置管理、命名服務(wù)、分布式鎖、集群管理和Leader選舉等場景。通過Spring Cloud Zookeeper,可以簡化與Zookeeper的集成,實(shí)現(xiàn)分布式系統(tǒng)的高可用性和一致性。
使用Zookeeper實(shí)現(xiàn)分布式鎖的方案
臨時無序節(jié)點(diǎn) + 重試(自旋)- 非公平鎖
實(shí)現(xiàn)步驟
初始化鎖目錄
確保鎖目錄存在,例如 /locks。如果不存在,則創(chuàng)建。 創(chuàng)建臨時無序節(jié)點(diǎn)
每個客戶端嘗試在 /locks 目錄下創(chuàng)建一個臨時無序節(jié)點(diǎn),表示鎖的請求。 String lockPath = zk.create("/locks/lock-", new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL);
嘗試獲取鎖
獲取 /locks 目錄下所有子節(jié)點(diǎn),檢查當(dāng)前是否只有自己一個節(jié)點(diǎn)存在。 List
if (children.size() == 1 && children.contains(lockPath.substring("/locks/".length()))) {
// 獲取到鎖
} else {
// 沒有獲取到鎖,自旋重試
}
自旋重試
如果沒有獲取到鎖,則進(jìn)行自旋重試,直到獲取到鎖為止。 while (true) {
List
if (children.size() == 1 && children.contains(lockPath.substring("/locks/".length()))) {
// 獲取到鎖
break;
} else {
// 等待一段時間再重試
Thread.sleep(100);
}
}
釋放鎖
刪除自己創(chuàng)建的臨時節(jié)點(diǎn),釋放鎖。 zk.delete(lockPath, -1);
代碼示例
以下是一個簡單的分布式鎖實(shí)現(xiàn)示例:
import org.apache.zookeeper.*;
import org.apache.zookeeper.data.Stat;
import java.util.List;
public class DistributedLock {
private ZooKeeper zk;
private String lockPath;
private static final int SESSION_TIMEOUT = 30000;
public DistributedLock(String zkHost) throws Exception {
this.zk = new ZooKeeper(zkHost, SESSION_TIMEOUT, event -> {});
Stat stat = zk.exists("/locks", false);
if (stat == null) {
zk.create("/locks", new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
}
}
public void acquireLock() throws Exception {
lockPath = zk.create("/locks/lock-", new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL);
while (true) {
List
if (children.size() == 1 && children.contains(lockPath.substring("/locks/".length()))) {
System.out.println("Acquired lock: " + lockPath);
return;
} else {
Thread.sleep(100);
}
}
}
public void releaseLock() throws Exception {
zk.delete(lockPath, -1);
System.out.println("Released lock: " + lockPath);
}
}
優(yōu)點(diǎn)
實(shí)現(xiàn)簡單:無需處理復(fù)雜的順序和監(jiān)聽邏輯,代碼簡潔明了。適用于非公平鎖場景:在一些應(yīng)用場景中,鎖的公平性并不是必須的,這種實(shí)現(xiàn)方法可能會更適用。
缺點(diǎn)
不公平:鎖的獲取順序不保證先來先得,可能會導(dǎo)致饑餓現(xiàn)象。性能問題:自旋重試機(jī)制在高并發(fā)場景下可能會增加Zookeeper的負(fù)載和網(wǎng)絡(luò)流量。資源浪費(fèi):自旋重試會導(dǎo)致資源浪費(fèi),特別是在鎖競爭激烈的情況下,頻繁的重試會占用大量CPU和網(wǎng)絡(luò)資源。
這種非公平鎖的實(shí)現(xiàn)適合一些對鎖公平性要求不高的應(yīng)用場景,但在高并發(fā)和資源競爭激烈的場景下,可能需要考慮更復(fù)雜的實(shí)現(xiàn)方法,如臨時順序節(jié)點(diǎn)和監(jiān)聽器結(jié)合的方法。
臨時順序節(jié)點(diǎn) + watch - 公平鎖
實(shí)現(xiàn)步驟
初始化鎖目錄
確保鎖目錄存在,例如 /locks。如果不存在,則創(chuàng)建。 創(chuàng)建臨時順序節(jié)點(diǎn)
每個客戶端嘗試在 /locks 目錄下創(chuàng)建一個臨時順序節(jié)點(diǎn),表示鎖的請求。 String lockPath = zk.create("/locks/lock-", new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
獲取所有子節(jié)點(diǎn)并排序
獲取 /locks 目錄下所有子節(jié)點(diǎn),并按順序排序。 List
Collections.sort(children);
判斷是否獲取到鎖
如果當(dāng)前節(jié)點(diǎn)是最小節(jié)點(diǎn),則獲取到鎖。如果不是最小節(jié)點(diǎn),則找到比當(dāng)前節(jié)點(diǎn)小的前一個節(jié)點(diǎn),并監(jiān)聽該節(jié)點(diǎn)的刪除事件。 String thisNode = lockPath.substring("/locks/".length());
int index = children.indexOf(thisNode);
if (index == 0) {
// 獲取到鎖
} else {
String prevNode = children.get(index - 1);
Stat stat = zk.exists("/locks/" + prevNode, new Watcher() {
@Override
public void process(WatchedEvent event) {
if (event.getType() == Event.EventType.NodeDeleted) {
// 上一個節(jié)點(diǎn)被刪除,嘗試獲取鎖
acquireLock();
}
}
});
if (stat == null) {
// 上一個節(jié)點(diǎn)已經(jīng)不存在,重試獲取鎖
acquireLock();
}
}
釋放鎖
刪除自己創(chuàng)建的臨時順序節(jié)點(diǎn),釋放鎖。 zk.delete(lockPath, -1);
代碼示例
以下是一個簡單的公平鎖實(shí)現(xiàn)示例:
import org.apache.zookeeper.*;
import org.apache.zookeeper.data.Stat;
import java.util.Collections;
import java.util.List;
public class DistributedFairLock {
private ZooKeeper zk;
private String lockPath;
private String thisNode;
private static final int SESSION_TIMEOUT = 30000;
public DistributedFairLock(String zkHost) throws Exception {
this.zk = new ZooKeeper(zkHost, SESSION_TIMEOUT, event -> {});
Stat stat = zk.exists("/locks", false);
if (stat == null) {
zk.create("/locks", new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
}
}
public void acquireLock() throws Exception {
this.lockPath = zk.create("/locks/lock-", new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
this.thisNode = lockPath.substring("/locks/".length());
while (true) {
List
Collections.sort(children);
int index = children.indexOf(thisNode);
if (index == 0) {
System.out.println("Acquired lock: " + lockPath);
return;
} else {
String prevNode = children.get(index - 1);
Stat stat = zk.exists("/locks/" + prevNode, new Watcher() {
@Override
public void process(WatchedEvent event) {
if (event.getType() == Event.EventType.NodeDeleted) {
try {
acquireLock();
} catch (Exception e) {
e.printStackTrace();
}
}
}
});
if (stat == null) {
acquireLock();
} else {
synchronized (this) {
wait();
}
}
}
}
}
public void releaseLock() throws Exception {
zk.delete(lockPath, -1);
System.out.println("Released lock: " + lockPath);
}
}
優(yōu)點(diǎn)
公平性:鎖的獲取順序嚴(yán)格按照節(jié)點(diǎn)創(chuàng)建的順序,保證公平性。效率高:只有在前一個節(jié)點(diǎn)釋放鎖時,才會嘗試獲取鎖,減少不必要的重試。
缺點(diǎn)
復(fù)雜度高:實(shí)現(xiàn)相對復(fù)雜,需要處理節(jié)點(diǎn)的監(jiān)聽和重試邏輯。ZooKeeper負(fù)載:在高并發(fā)場景下,ZooKeeper的負(fù)載可能較高。
這種實(shí)現(xiàn)方式適合對鎖的公平性要求較高的應(yīng)用場景,如訂單系統(tǒng)、隊列系統(tǒng)等。通過臨時順序節(jié)點(diǎn)和Watch機(jī)制,確保了鎖的獲取順序,有效避免了饑餓現(xiàn)象。
柚子快報邀請碼778899分享:Zookeeper
精彩鏈接
本文內(nèi)容根據(jù)網(wǎng)絡(luò)資料整理,出于傳遞更多信息之目的,不代表金鑰匙跨境贊同其觀點(diǎn)和立場。
轉(zhuǎn)載請注明,如有侵權(quán),聯(lián)系刪除。