摘要:但同樣又會出現(xiàn)新的問題,如果服務提供者的節(jié)點新增或者刪除消費者這邊根本就不知道情況。通常來說消費者是需要知道服務提供者的網(wǎng)絡地址才能發(fā)起遠程調用,這塊內容和我上面的需求其實非常類似。
背景
最近在做分布式相關的工作,由于人手不夠只能我一個人來懟;看著這段時間的加班表想想就是夠慘的。
不過其中也有遇到的不少有意思的事情今后再拿來分享,今天重點來討論服務的注冊與發(fā)現(xiàn)。
分布式帶來的問題我的業(yè)務比較簡單,只是需要知道現(xiàn)在有哪些服務實例可供使用就可以了(并不是做遠程調用,只需要拿到信息即可)。
要實現(xiàn)這一功能最簡單的方式可以在應用中配置所有的服務節(jié)點,這樣每次在使用時只需要通過某種算法從配置列表中選擇一個就可以了。
但這樣會有一個非常嚴重的問題:
由于應用需要根據(jù)應用負載情況來靈活的調整服務節(jié)點的數(shù)量,這樣我的配置就不能寫死。
不然就會出現(xiàn)要么新增的節(jié)點沒有訪問或者是已經(jīng) down 掉的節(jié)點卻有請求,這樣肯定是不行的。
往往要解決這類分布式問題都需要一個公共的區(qū)域來保存這些信息,比如是否可以利用 Redis?
每個節(jié)點啟動之后都向 Redis 注冊信息,關閉時也刪除數(shù)據(jù)。
其實就是存放節(jié)點的 ip + port,然后在需要知道服務節(jié)點信息時候只需要去 Redis 中獲取即可。
如下圖所示:
但這樣會導致每次使用時都需要頻繁的去查詢 Redis,為了避免這個問題我們可以在每次查詢之后在本地緩存一份最新的數(shù)據(jù)。這樣優(yōu)先從本地獲取確實可以提高效率。
但同樣又會出現(xiàn)新的問題,如果服務提供者的節(jié)點新增或者刪除消費者這邊根本就不知道情況。
要解決這個問題最先想到的應該就是利用定時任務定期去更新服務列表。
以上的方案肯定不完美,并且不優(yōu)雅。主要有以下幾點:
基于定時任務會導致很多無效的更新。
定時任務存在周期性,沒法做到實時,這樣就可能存在請求異常。
如果服務被強行 kill,沒法及時清除 Redis,這樣這個看似可用的服務將永遠不可用!
所以我們需要一個更加靠譜的解決方案,這樣的場景其實和 Dubbo 非常類似。
用過的同學肯定對這張圖不陌生。
引用自 Dubbo 官網(wǎng)
其中有一塊非常核心的內容(紅框出)就是服務的注冊與發(fā)現(xiàn)。
通常來說消費者是需要知道服務提供者的網(wǎng)絡地址(ip + port)才能發(fā)起遠程調用,這塊內容和我上面的需求其實非常類似。
而 Dubbo 則是利用 Zookeeper 來解決問題。
Zookeeper 能做什么在具體討論怎么實現(xiàn)之前先看看 Zookeeper 的幾個重要特性。
Zookeeper 實現(xiàn)了一個類似于文件系統(tǒng)的樹狀結構:
這些節(jié)點被稱為 znode(名字叫什么不重要),其中每個節(jié)點都可以存放一定的數(shù)據(jù)。
最主要的是 znode 有四種類型:
永久節(jié)點(除非手動刪除,節(jié)點永遠存在)
永久有序節(jié)點(按照創(chuàng)建順序會為每個節(jié)點末尾帶上一個序號如:root-1)
瞬時節(jié)點(創(chuàng)建客戶端與 Zookeeper 保持連接時節(jié)點存在,斷開時則刪除并會有相應的通知)
瞬時有序節(jié)點(在瞬時節(jié)點的基礎上加上了順序)
考慮下上文使用 Redis 最大的一個問題是什么?
其實就是不能實時的更新服務提供者的信息。
那利用 Zookeeper 是怎么實現(xiàn)的?
主要看第三個特性:瞬時節(jié)點
Zookeeper 是一個典型的觀察者模式。
由于瞬時節(jié)點的特點,我們的消費者可以訂閱瞬時節(jié)點的父節(jié)點。
當新增、刪除節(jié)點時所有的瞬時節(jié)點也會自動更新。
更新時會給訂閱者發(fā)起通知告訴最新的節(jié)點信息。
這樣我們就可以實時獲取服務節(jié)點的信息,同時也只需要在第一次獲取列表時緩存到本地;也不需要頻繁和 Zookeeper 產(chǎn)生交互,只用等待通知更新即可。
并且不管應用什么原因節(jié)點 down 掉后也會在 Zookeeper 中刪除該信息。
效果演示這樣實現(xiàn)方式就變?yōu)檫@樣。
為此我新建了一個應用來進行演示:
https://github.com/crossoverJie/netty-action/tree/master/netty-action-zk
就是一個簡單的 SpringBoot 應用,只是做了幾件事情。
應用啟動時新開一個線程用于向 Zookeeper 注冊服務。
同時監(jiān)聽一個節(jié)點用于更新本地服務列表。
提供一個接口用于返回一個可有的服務節(jié)點。
我在本地啟動了兩個應用分別是:127.0.0.1:8083,127.0.0.1:8084。來看看效果圖。
兩個應用啟動完成:
當前 Zookeeper 的可視化樹狀結構:
當想知道所有的服務節(jié)點信息時:
想要獲取一個可用的服務節(jié)點時:
這里只是采取了簡單的輪詢。
當 down 掉一個節(jié)點時:應用會收到通知更新本地緩存。同時 Zookeeper 中的節(jié)點會自動刪除。
再次獲取最新節(jié)點時:
當節(jié)點恢復時自然也能獲取到最新信息。本地緩存也會及時更新。
編碼實現(xiàn)實現(xiàn)起來倒也比較簡單,主要就是 ZKClient 的 api 使用。
貼幾段比較核心的吧。
注冊啟動注冊 Zookeeper。
主要邏輯都在這個線程中。
首先創(chuàng)建父節(jié)點。如上圖的 Zookeeper 節(jié)點所示;需要先創(chuàng)建 /route 根節(jié)點,創(chuàng)建的時候會判斷是否已經(jīng)存在。
接著需要判斷是否需要將自己注冊到 Zookeeper 中,因為有些節(jié)點只是用于服務發(fā)現(xiàn),他自身是不需要承擔業(yè)務功能(是我自己項目的需求)。
將當前應用的所在 ip 以及端口注冊上去,同時需要監(jiān)聽根節(jié)點 /route ,這樣才能在其他服務上下線時候獲得通知。
根據(jù)本地緩存監(jiān)聽到服務變化
public void subscribeEvent(String path) { zkClient.subscribeChildChanges(path, new IZkChildListener() { @Override public void handleChildChange(String parentPath, ListcurrentChilds) throws Exception { logger.info("清除/更新本地緩存 parentPath=【{}】,currentChilds=【{}】", parentPath,currentChilds.toString()); //更新所有緩存/先刪除 再新增 serverCache.updateCache(currentChilds) ; } }); }
可以看到這里是更新了本地緩存,該緩存采用了 Guava 提供的 Cache,感興趣的可以查看之前的源碼分析。
/** * 更新所有緩存/先刪除 再新增 * * @param currentChilds */ public void updateCache(List客戶端負載currentChilds) { cache.invalidateAll(); for (String currentChild : currentChilds) { String key = currentChild.split("-")[1]; addCache(key); } }
同時在客戶端提供了一個負載算法。
其實就是一個輪詢的實現(xiàn):
/** * 選取服務器 * * @return */ public String selectServer() { Listall = getAll(); if (all.size() == 0) { throw new RuntimeException("路由列表為空"); } Long position = index.incrementAndGet() % all.size(); if (position < 0) { position = 0L; } return all.get(position.intValue()); }
當然這里可以擴展出更多的如權重、隨機、LRU 等算法。
Zookeeper 其他優(yōu)勢及問題Zookeeper 自然是一個很棒的分布式協(xié)調工具,利用它的特性還可以有其他作用。
數(shù)據(jù)變更發(fā)送通知這一特性可以實現(xiàn)統(tǒng)一配置中心,再也不需要在每個服務中多帶帶維護配置。
利用瞬時有序節(jié)點還可以實現(xiàn)分布式鎖。
在實現(xiàn)注冊、發(fā)現(xiàn)這一需求時,Zookeeper 其實并不是最優(yōu)選。
由于 Zookeeper 在 CAP 理論中選擇了 CP(一致性、分區(qū)容錯性),當 Zookeeper 集群有半數(shù)節(jié)點不可用時是不能獲取到任何數(shù)據(jù)的。
對于一致性來說自然沒啥問題,但在注冊、發(fā)現(xiàn)的場景下更加推薦 Eureka,已經(jīng)在 SpringCloud 中得到驗證。具體就不在本文討論了。
但鑒于我的使用場景來說 Zookeeper 已經(jīng)能夠勝任。
總結本文所有完整代碼都托管在 GitHub。
https://github.com/crossoverJie/netty-action。
一個看似簡單的注冊、發(fā)現(xiàn)功能實現(xiàn)了,但分布式應用遠遠不止這些。
由于網(wǎng)絡隔離之后帶來的一系列問題還需要我們用其他方式一一完善;后續(xù)會繼續(xù)更新分布式相關內容,感興趣的朋友不妨持續(xù)關注。
你的點贊與轉發(fā)是最大的支持。
文章版權歸作者所有,未經(jīng)允許請勿轉載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉載請注明本文地址:http://m.hztianpu.com/yun/76869.html
摘要:背景基于負載均衡的服務調用基于負載均衡的服務相互調用指的是通過基于等負載均衡軟件來構建一個負載均衡服務,所有的服務調用都通過負載均衡器從負載均衡的這種模式下其實有兩個主要的問題一是中心化,整個系統(tǒng)都基于負載均衡器,負載均衡就相當于整個業(yè)背景 基于負載均衡的服務調用 showImg(https://user-gold-cdn.xitu.io/2019/5/23/16ae29255c5f47b1...
摘要:為安裝文件,無需再配置環(huán)境變量。連接操作有以下包作者并未查到除此之外的包,但不代表沒有。等于是每個默認配置的主鍵屬性,屬性名為可自己定義一個來覆蓋此屬性。需要注意的是,在新版本的文檔中,為。通過創(chuàng)建限于篇幅,本小節(jié)暫時寫到這里。 我的琴聲嗚咽,我的淚水全無。我把遠方的遠歸還草原?! ? 海子《九月》 mongodb安裝 什么是Mongodb?就是一個基...
摘要:一個軟件測試在職老人幫你詳細分析一下。在軟件測試行業(yè),前兩點可以結合起來說,就是大環(huán)境和前景以及人才缺口的問題。軟件測試屬于互聯(lián)網(wǎng)技術的一個分支,就是經(jīng)常被提到的行業(yè)。你零基礎轉行嗷,良心奉勸你不要自學。 一個軟件測試在職老人幫你詳細分析一下。先不說軟件測試領域,你想轉行的話,得知道這個行...
閱讀 4184·2021-10-08 10:12
閱讀 5156·2021-09-02 15:40
閱讀 1088·2021-09-01 11:09
閱讀 1703·2021-08-31 09:38
閱讀 2618·2019-08-30 13:54
閱讀 2378·2019-08-30 12:54
閱讀 1310·2019-08-30 11:18
閱讀 1483·2019-08-29 14:06