基于Zookeeper實現(xiàn)服務(wù)注冊和服務(wù)發(fā)現(xiàn)功能
前言
無論是采用SOA還是微服務(wù)架構(gòu),都需要使用服務(wù)注冊和服務(wù)發(fā)現(xiàn)組件。我剛開始接觸 Dubbo 時一直對服務(wù)注冊/發(fā)現(xiàn)以及 Zookeeper 的作用感到困惑,現(xiàn)在看來是因為對分布式系統(tǒng)的理解不夠深入,對 Dubbo 和 Zookeeper 的工作原理不夠清楚。
本文將基于 Zookeeper 實現(xiàn)服務(wù)注冊和服務(wù)發(fā)現(xiàn)功能,如果跟我一樣有同樣的困惑,希望可以通過本文了解其他組件如何使用 Zookeeper 作為注冊中心的工作原理。
聲明
文章中所提供的代碼僅供參考,旨在幫助缺乏基礎(chǔ)知識的開發(fā)人員更好地理解服務(wù)注冊和服務(wù)發(fā)現(xiàn)的概念。請注意,這些代碼并不適用于實際應(yīng)用中。
前置知識
服務(wù)注冊和發(fā)現(xiàn)
在SOA或微服務(wù)架構(gòu)中,由于存在大量的服務(wù)以及可能的相互調(diào)用,為了更有效地管理這些服務(wù),我們通常需要引入一個統(tǒng)一的地方,即注冊中心,來集中管理它們,而注冊中心最基本的功能就是服務(wù)注冊/發(fā)現(xiàn)。
- 服務(wù)注冊:將該服務(wù)實例的元數(shù)據(jù)(如IP地址、端口號、健康狀態(tài)等)注冊到注冊中心,這樣其他服務(wù)或客戶端可以發(fā)現(xiàn)和使用該服務(wù)。
- 服務(wù)發(fā)現(xiàn):當(dāng)一個服務(wù)需要調(diào)用別的服務(wù)時,使用靜態(tài)配置是不可行的,這個時候可以去注冊中心獲取可用的服務(wù)實例并調(diào)用。
Zookeeper
Zookeeper 是一個傳統(tǒng)的分布式協(xié)調(diào)服務(wù),它更多的被用來作為一個協(xié)調(diào)器使用,比如來協(xié)調(diào)管理 Hadoop 集群、協(xié)調(diào) Kafka 的 leader 選舉等。
為什么會有組件將其視為一個注冊中心使用?我想有幾個原因:
- Zookeeper 在分布式系統(tǒng)中具有更強(qiáng)的一致性和可靠性,可以確保各個服務(wù)的注冊信息保持一致。
- Zookeeper 使用內(nèi)存存儲數(shù)據(jù),具有很高的讀寫性能。這對于注冊中心來說非常關(guān)鍵,因為它需要快速地響應(yīng)客戶端的請求。
- Zookeeper 的 Watcher 機(jī)制可以讓客戶端監(jiān)聽指定節(jié)點的變化。當(dāng)某個節(jié)點(注冊中心)發(fā)生變化時,Zookeeper 可以通知其他服務(wù)實現(xiàn)實時更新。
工作原理
以下圖為例,可以看到 Dubbo 是如何使用 Zookeeper 實現(xiàn)服務(wù)注冊/發(fā)現(xiàn)的。
- 服務(wù)提供者向 /dubbo/com.foo.BarService/providers 目錄下寫入自己的 URL 地址。
- 服務(wù)消費者訂閱 /dubbo/com.foo.BarService/providers 目錄下的提供者 URL 地址。并向 /dubbo/com.foo.BarService/consumers 目錄下寫入自己的 URL 地址。
這里的目錄就是 Zookeeper 的數(shù)據(jù)結(jié)構(gòu),原理非常簡單,本質(zhì)上就是服務(wù)提供者和消費者按照約定在 Zookeeper 上讀寫數(shù)據(jù),同時借用其 Watcher 機(jī)制、臨時節(jié)點和可靠性等特性高效的實現(xiàn)以下功能:
- 當(dāng)提供者服務(wù)出現(xiàn)斷電等異常停機(jī)時,注冊中心能自動刪除提供者信息。
- 當(dāng)注冊中心重啟時,能自動恢復(fù)注冊數(shù)據(jù)以及訂閱請求。
實現(xiàn)過程
注冊中心
下面通過 Zookeeper 的 Java API 實現(xiàn)一個只包含服務(wù)注冊/發(fā)現(xiàn)的注冊中心,代碼如下:
public class RegistrationCenter { // 連接信息 private String connectString = "192.168.10.11:2181,192.168.10.11:2182,192.168.10.11:2183"; // 超時時間 private int sessionTimeOut = 30000; private final String ROOT_PATH = "/servers"; private ZooKeeper client; public RegistrationCenter() { this(null); } public RegistrationCenter(Consumer<List<String>> consumer) { try { getConnection(null == consumer ? null : watchedEvent -> { //監(jiān)聽服務(wù)器地址的上下線 if (watchedEvent.getType() == Watcher.Event.EventType.NodeChildrenChanged) { try { consumer.accept(subServers()); } catch (Exception e) { e.printStackTrace(); } } }); Stat stat = client.exists(ROOT_PATH, false); if (stat == null) { //創(chuàng)建根節(jié)點 client.create(ROOT_PATH, ROOT_PATH.getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); } } catch (Exception e) { e.printStackTrace(); } } /** * @param serverName 將服務(wù)器注冊到zk集群時,所需的服務(wù)名稱 * @param metadata 服務(wù)元數(shù)據(jù) * @throws Exception */ public void doRegister(String serverName, Metadata metadata) throws Exception { /** * ZooDefs.Ids.OPEN_ACL_UNSAFE: 此權(quán)限表示允許所有人訪問該節(jié)點(服務(wù)器) * CreateMode.EPHEMERAL_SEQUENTIAL: 由于服務(wù)器是動態(tài)上下線的,上線后存在,下線后不存在,所以是臨時節(jié)點 * 而服務(wù)器一般都是有序號的,所以是臨時、有序的節(jié)點. */ String node = client.create(ROOT_PATH + "/" + serverName, metadata.toString().getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL); System.out.println(serverName + " 已經(jīng)上線"); } /** * 發(fā)現(xiàn)/訂閱服務(wù) */ public List<String> subServers() throws InterruptedException, KeeperException { List<String> zkChildren = client.getChildren(ROOT_PATH, true); List<String> servers = new ArrayList<>(); zkChildren.forEach(node -> { //拼接服務(wù)完整信息 try { byte[] data = client.getData(ROOT_PATH + "/" + node, false, null); servers.add(new String(data)); } catch (Exception e) { e.printStackTrace(); } }); return servers; } private void getConnection(Watcher watcher) throws IOException { this.client = new ZooKeeper(connectString, sessionTimeOut, watcher); } /** * 服務(wù)元數(shù)據(jù) */ public static class Metadata { public Metadata() { } public Metadata(String ip, int port) { this.ip = ip; this.port = port; } private String ip; private int port; public String getIp() { return ip; } public void setIp(String ip) { this.ip = ip; } public int getPort() { return port; } public void setPort(int port) { this.port = port; } @Override public String toString() { return "{" + "ip='" + ip + '\'' + ", port=" + port + '}'; } } }
該類中兩個核心方法:doRegister()
、subServers()
服務(wù)注冊和訂閱。
doRegister()
主要是往 Zookeeper 中創(chuàng)建了一個臨時節(jié)點數(shù)據(jù),臨時節(jié)點的優(yōu)勢就是當(dāng)服務(wù)出現(xiàn)斷電等異常停機(jī)時,節(jié)點會自動刪除。subServers()
則是去 Zookeeper 讀取了所有服務(wù)提供者的信息,并且監(jiān)聽了節(jié)點狀態(tài),當(dāng)節(jié)點發(fā)生創(chuàng)建、刪除、更新等事件時重新獲取服務(wù)者的信息,做到數(shù)據(jù)實時更新。
至此,一個簡單的注冊中心就完成了,當(dāng)然,如果要實現(xiàn)一個成熟的注冊中心,還要考慮負(fù)載均衡、高可用性和容錯、服務(wù)治理和路由控制等功能,這里先不展開。
服務(wù)注冊
當(dāng)有了注冊中心,服務(wù)提供者就可以調(diào)用 doRegister()
進(jìn)行注冊,代碼如下:
public class ProviderServer { public static void main(String[] args) throws Exception { RegistrationCenter registrationCenter = new RegistrationCenter(); registrationCenter.doRegister("provider", new RegistrationCenter.Metadata("127.0.0.1", 8080)); Thread.sleep(Long.MAX_VALUE); } }
服務(wù)發(fā)現(xiàn)
同樣,服務(wù)消費者可以調(diào)用 subServers()
發(fā)現(xiàn)服務(wù)提供者,同時當(dāng)服務(wù)提供者發(fā)生變化時會通知到消費者。代碼如下:
public class ConsumerServer { public static void main(String[] args) throws Exception { RegistrationCenter registrationCenter = new RegistrationCenter(newServers -> { System.out.println("服務(wù)更新了..."+newServers); }); List<String> servers = registrationCenter.subServers(); System.out.println(servers); Thread.sleep(Long.MAX_VALUE); } }
總結(jié)
服務(wù)注冊和服務(wù)發(fā)現(xiàn)功能是為了解決分布式系統(tǒng)中的服務(wù)管理和通信問題而設(shè)計的,經(jīng)過不斷的發(fā)展與負(fù)載均衡、健康監(jiān)測、服務(wù)治理和路由控制等功能完善成為一個注冊中心。服務(wù)注冊和服務(wù)發(fā)現(xiàn)有助于實現(xiàn)系統(tǒng)的彈性和可擴(kuò)展性,因為新的服務(wù)實例可以動態(tài)地加入系統(tǒng),而無需手動配置和修改已有的代碼。
以上就是基于Zookeeper實現(xiàn)服務(wù)注冊和服務(wù)發(fā)現(xiàn)功能的詳細(xì)內(nèi)容,更多關(guān)于Zookeeper服務(wù)注冊和發(fā)現(xiàn)的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
knife4j3.0.3整合gateway和注冊中心的詳細(xì)過程
這篇文章主要介紹了knife4j3.0.3整合gateway和注冊中心的詳細(xì)過程,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2023-03-03Spring超詳細(xì)講解事務(wù)和事務(wù)傳播機(jī)制
Spring事務(wù)的本質(zhì)就是對數(shù)據(jù)庫事務(wù)的支持,沒有數(shù)據(jù)庫事務(wù),Spring是無法提供事務(wù)功能的。Spring只提供統(tǒng)一的事務(wù)管理接口,具體實現(xiàn)都是由數(shù)據(jù)庫自己實現(xiàn)的,Spring會在事務(wù)開始時,根據(jù)當(dāng)前設(shè)置的隔離級別,調(diào)整數(shù)據(jù)庫的隔離級別,由此保持一致2022-06-06@Async導(dǎo)致controller?404及失效原因解決分析
這篇文章主要為大家介紹了@Async導(dǎo)致controller?404失效問題解決,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-07-07Springboot優(yōu)化內(nèi)置服務(wù)器Tomcat優(yōu)化方式(underTow)
本文詳細(xì)介紹了Spring Boot中Tomcat和Undertow服務(wù)器的配置和優(yōu)化,包括初始線程數(shù)、最大線程數(shù)、最小備用線程數(shù)、最大請求數(shù)等參數(shù)的優(yōu)化建議,以及在高并發(fā)場景下Undertow相對于Tomcat的優(yōu)勢2024-12-12idea導(dǎo)入工程時不能導(dǎo)入maven項目不能加入tomcatServer的原因
這篇文章主要介紹了idea導(dǎo)入工程時不能導(dǎo)入maven項目不能加入tomcatServer的原因及解決方法,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-09-09