簡單聊聊服務發現(redis, zk,etcd, consul)

什麼是服務發現?

 

服務發現並無怎樣的高深莫測,它的原理再簡單不過。只是市面上太多文章將服務發現的難度妖魔化,讀者被繞的雲裏霧裏,頓覺本身智商低下不敢高攀。git

服務提供者是什麼,簡單點說就是一個HTTP服務器,提供了API服務,有一個IP端口做爲服務地址。服務消費者是什麼,它就是一個簡單的進程,想要訪問服務提供者提供的服務來幹一些事情。一個HTTP服務器既能夠是服務提供者對外提供服務,也能夠是消費者須要別的服務提供者提供的服務,這就是服務依賴,沒有你我就不是我本身。複雜的服務甚至有多個服務依賴。github

服務發現有三個角色,服務提供者、服務消費者和服務中介。服務中介是聯繫服務提供者和服務消費者的橋樑。服務提供者將本身提供的服務地址註冊到服務中介,服務消費者從服務中介那裏查找本身想要的服務的地址,而後享受這個服務。服務中介提供多個服務,每一個服務對應多個服務提供者。redis

 
 

服務中介就是一個字典,字典裏有不少key/value鍵值對,key是服務名稱,value是服務提供者的地址列表。服務註冊就是調用字典的Put方法塞東西,服務查找就是調用字典的Get方法拿東西。數據庫

當服務提供者節點掛掉時,要求服務可以及時取消註冊,比便及時通知消費者從新獲取服務地址。服務器

當服務提供者新加入時,要求服務中介能及時告知服務消費者,你要不要嘗試一下新的服務。網絡

Redis做爲服務中介數據結構

Redis裏面有豐富的數據結構,拿來存儲服務字典再合適不過了。對每個服務名稱,咱們用一個set結構存儲服務的IP:Port字符串。若是服務提供者加入,調用sadd命令加入服務地址,若是服務掛掉,調用srem命令移除服務地址。對服務消費者使用smembers指令獲取全部服務地址而後在消費進程裏隨機挑一個,或者使用srandmemember指令直接獲取隨機服務地址。分佈式

這個時候你也許會表示懷疑,服務發現真這麼簡單麼?答案是還差一點,關於上面的這個解決方案有幾個問題。學習

第一個問題是服務提供者進程若是被kill -9暴力殺死,不能主動調用srem命令怎麼辦?spa

這個時候服務列表中多了一個黑地址指向了不存在的服務而消費者徹底不知道,這個時候服務中介就成了黑中介了。那該怎麼辦呢?

咱們引入服務保活和檢查機制,並更換數據結構。服務提供者須要每隔5秒左右向服務中介彙報存活,服務中介將服務地址和彙報時間記錄在zset數據結構的value和score中。服務中介須要每隔10秒左右檢查zset數據結構,踢掉彙報時間嚴重落後的服務地址項。這樣就能夠準實時地保證服務列表中服務地址的有效性。

第二個問題是服務列表變更時如何通知消費者。有兩種解決方案。

第一種是輪詢,消費者須要每隔幾秒查詢服務列表是否有改變。若是服務不少,服務列表很大,消費者不少,redis會有必定壓力。因此這時候能夠引入服務列表的版本號機制,給每一個服務提供一個key/value設置服務的版本號,就是在服務列表發生變更時,遞增這個版本號。消費者只須要輪詢這個版本號的變更便可知道服務列表是否發生了變化。由於服務列表比較穩定,僅在網絡嚴重抖動的狀況下才會頻繁發生變更,因此redis幾乎沒有壓力。

第二種是採用pubsub。這種方式及時性要明顯好於輪詢。缺點是每一個pubsub都會佔用消費者一個線程和一個額外的redis鏈接。爲了減小對線程和鏈接的浪費,咱們使用單個pubsub廣播全局版本號的變更。所謂全局版本號就是任意服務列表發生了變更,這個版本號都會遞增。接收到版本變更的消費者再去檢查各自的依賴服務列表的版本號是否發生了變更。這種全局版本號也能夠用於第一種輪詢方案。

第三個問題是redis是單點的,若是掛掉了怎麼辦?

這是個大問題。正是由於這個問題的存在,流行的服務發現系統都是使用分佈式數據庫zookeeper/etcd/consul等來做爲服務中介,它們是分佈式的多節點的,掛掉了一個節點不要緊,系統仍然能夠正常工做。

 
 

那若是整個zk集羣掛掉會怎樣呢?其實每一個服務消費者在本地內存裏都會存一份當前的服務列表,即便服務中介集羣掛掉,也是可使用當前的服務列表正常工做的。

那redis做爲服務中介就真的不靠譜了麼?其實還有個redis-sentinel能夠消除redis的單點問題,redis-sentinel能夠在主節點掛掉的時候,自動升級從節點爲主節點。因此拿redis幹這件事也是能夠的。用redis幹服務發現確實很是簡單,雖然這種方式很是不流行。

服務提供者不僅是HTTP服務

上面提到服務提供者簡單來講就是HTTP服務器,其實服務多種多樣。能夠是數據庫服務,能夠是RPC服務,能夠是UDP服務等等。

若是是MySQL數據庫,那如何將MySQL服務註冊到服務中介呢?原生的MySQL可沒有提供這樣功能。通常作法是提供一個Agent代理去註冊。這個代理除了將服務地址註冊到服務中介外,還須要監控MySQL的健康情況,以便當MySQL宕機時能及時切換到新的MySQL服務地址。通常這個Agent爲了節省資源而不止監控一個數據庫,它能夠同時監控多個數據庫,甚至是多種數據庫。

服務配置重加載

服務發現通常只是用來註冊和查找服務列表這樣一個比較單純的功能。不過現代的服務發現系統還會集成服務配置管理功能。這樣能夠實現服務配置的實時重加載。原理也很簡單,就是對於每個服務項,服務中介還會存儲一個單獨的key/value用來存儲這個服務的配置信息。當這個配置項在後臺被修改時,服務中介會實時通知相關服務器變動配置信息。好比數據庫地址變更,業務參數修改等。

服務管理後臺

爲了便於服務管理,通常服務發現還會提供一個服務管理後臺,用於管理人員查看服務集羣的狀態。若是服務註冊和彙報時提供冗餘的配置信息,服務管理後臺就能夠呈現更爲詳細的服務信息。服務管理後臺還能夠將全部的服務依賴組織起來,呈現出一顆漂亮的服務依賴樹。

服務發現的一個簡單實現

小編在閒暇之餘基於Redis實現了一個簡單的服務發現系統Captain。讀者能夠去github上下載這個項目進行學習。我除了編寫了服務發現的服務器以外,客戶端sdk也一塊作了開發,可能不太穩定,但願讀者體諒,不要用於線上的業務系統。

 
 

在Captain這個項目裏,個人服務發現服務器將Redis提供的服務作了一層封裝,對外提供HTTP API進行服務的註冊和查找,沒有使用上文提到的pubsub功能。

參考:
https://zhuanlan.zhihu.com/p/34332329

相關文章
相關標籤/搜索