大綱
1.Nacos的定位和優勢
2.Nacos的整體架構
3.Nacos的配置模型
4.Nacos內核設計之一致性協議
5.Nacos內核設計之自研Distro協議
6.Nacos內核設計之通信通道
7.Nacos內核設計之尋址機制
8.服務注冊發現模塊的注冊中心的設計原理
9.服務注冊發現模塊的注冊中心的服務數據模型
10.服務注冊發現模塊的健康檢查機制
11.配置管理模塊的配置一致性模型
12.Zookeeper、Eureka和Nacos的對比總結
5.Nacos內核設計之自研Distro協議
(1)Distro協議的背景和設計思想
(2)Distro協議的工作原理之數據初始化
(3)Distro協議的工作原理之數據校驗
(4)Distro協議的工作原理之寫操作
(5)Distro協議的工作原理之讀操作
(6)總結
(1)Distro協議的背景和設計思想
一.Distro協議的背景
Distro協議是Nacos自研的?種AP分布式協議,也是面向臨時實例設計的?種分布式協議。它保證了在某些Nacos節點宕機后,整個臨時實例處理系統依舊可正常工作。
作為?種有狀態的中間件應用的內嵌協議,Distro協議保證了各個Nacos節點對于海量注冊請求的統?協調和存儲。
二.Distro協議的設計思想
設計一:Nacos每個節點是平等的
每個節點都會處理寫請求,以及都會把新數據同步到其他節點。
設計二:每個節點只負責部分數據
每個節點都會定時發送自己負責數據的校驗值到其他節點來保持數據?致性。
設計三:每個節點獨立處理讀請求
所以每個節點都能夠及時從本地發出響應。
(2)Distro協議的工作原理之數據初始化
新加入的Nacos節點會拉取全量數據。具體操作就是輪詢所有Nacos節點,向其他節點發送請求來拉取全量數據。
完成拉取全量數據后,Nacos的每個節點都擁有了當前所有注冊上來的非持久化實例數據。
(3)Distro協議的工作原理之數據校驗
在Nacos集群啟動后,各節點間會定期發送心跳。心跳信息主要是當前節點上的所有數據的元信息,使用元信息的原因是要保證在網絡中傳輸的數據量維持在?個較低水平。
這種數據校驗會以心跳的形式進行。即每個節點會在固定時間間隔,向其他節點發起?次數據校驗請求。在數據校驗過程中,一旦某節點發現其他節點上的數據與本地數據不?致,則會發起?次全量拉取請求,將數據補齊。
(4)Distro協議的工作原理之寫操作
對于?個已經啟動完成的Nacos集群,在?次客戶端發起寫操作的流程中,當注冊非持久化的服務實例的寫請求發送到某臺Nacos節點時,Nacos集群處理的流程如下:
一.前置的Filter攔截寫請求
首先,收到寫請求的Nacos節點,其前置Filter會攔截寫請求。然后根據請求中包含的IP和Port信息計算其所屬的Nacos責任節點,并將該請求轉發到所屬的Nacos責任節點上。
二.Controller會解析寫請求
所屬的Nacos責任節點收到寫請求后,會讓其Controller解析寫請求。
三.Distro協議下Nacos節點會定時執行同步任務
定時同步任務會將本機所負責的所有實例信息同步到其他節點上。
(5)Distro協議的工作原理之讀操作
由于Nacos的每個節點上都存放了全量數據,因此在每?次讀操作中,Nacos節點會直接從本地拉取數據進行快速響應。
這種機制保證了Distro協議作為?種AP協議,能及時響應讀操作的請求。在網絡分區情況下,對于所有的讀操作也能夠正常返回。當網絡分區恢復時,各Nacos節點都會把各數據分片的數據進行合并恢復。
(6)總結
Distro協議是Nacos對臨時服務實例數據開發的?致性協議。其數據存儲在緩存中,在啟動時進行全量數據同步,并定期進行數據校驗。在Distro協議的設計思想下,每個節點都可以接收和處理讀寫請求。
Distro協議的請求場景主要分為如下三種情況:
情況一:當節點接收到屬于自己負責的服務實例的寫請求時,直接寫入。
情況二:當節點接收到不屬于自己負責的服務實例的寫請求時,首先在集群內部路由,然后轉發給對應的節點,從而完成寫請求。
情況三:當節點接收到任何讀請求時,都直接在本機查詢并返回,因為所有服務實例的數據都被同步到了各機器節點上。
Distro協議作為Nacos臨時服務實例的?致性協議,保證了分布式環境下各節點上的服務信息狀態,都能及時通知其他節點。Distro協議可讓Nacos維持數十萬量級的服務實例數據的存儲和?致性。
6.Nacos內核設計之通信通道
(1)現狀背景
(2)場景分析
(3)長連接核心訴求
(4)長連接選型對比
(1)現狀背景
Nacos 1.x版本的Config/Naming模塊,各自的推送通道都是按照自己的設計模型來實現的。配置和服務器模塊的數據推送通道不統?,HTTP短連接性能壓力巨大,未來Nacos需構建能同時支持配置及服務的長連接通道,以標準的通信模型重構推送通道。
(2)場景分析
場景一:配置管理
一.Client和Server之間的通信
客戶端Client需要感知服務端Server的節點列表,需要按照某種策略選擇其中?個Server節點進行連接。當底層連接斷開時,客戶端Client就需要切換Server進行重連。
客戶端基于當前可用的長連接,進行配置的查詢、發布、刪除、監聽等RPC語義的接口通信。
服務端需要將配置變更消息推送給當前監聽的客戶端。當網絡不穩定時,客戶端接收失敗,就需要支持重推并告警。
服務端需要感知客戶端連接斷開事件,將連接注銷,并且清空連接對應的上下文。
二.Server之間的通信
每個Server都需要能夠獲取集群所有Server的地址列表,然后為每個Server創建獨立的長連接。當長連接斷開時,需要進行重連。當節點列表變更時,需要創建新節點的長連接和銷毀下線節點的長連接。
Server間需要進行數據同步,包括配置信息、當前連接數信息、系統負載信息、負載調節信息等同步。
場景二:服務注冊發現
一.Client和Server之間的通信
客戶端Client需要感知服務端Server的節點列表,需要按照某種策略選擇其中?個Server節點進行連接。當底層連接斷開時,客戶端Client就需要切換Server進行重連。
客戶端基于當前可用的長連接,進行服務的查詢、注冊、注銷、訂閱等RPC語義的接口通信。
服務端需要將服務變更消息推送新給客戶端,完成推送后客戶端需要ACK,方便服務端進行metrics統計和重推判定等。
服務端需要感知客戶端連接斷開事件,將連接注銷,并且清空連接對應的上下文。
二.Server之間的通信
服務端之間需要通過長連接感知對端存活狀態,需要通過長連接匯報服務狀態(同步RPC能力)。
服務端之間進行AP Distro數據同步,需要異步RPC帶ACK能力。
(3)長連接核心訴求
一.功能性訴求
二.性能要求
三.負載均衡
四.連接生命周期
一.功能性訴求
客戶端的訴求:
訴求一:實時感知連接生命周期的能力,包括連接建立、連接斷開事件。
訴求二:客戶端調用服務端支持同步阻塞、異步Future、異步CallBack三種模式。
訴求三:底層連接自動切換能力。
訴求四:響應服務端連接重置消息進行連接切換。
訴求五:選址/服務發現。
服務端的訴求:
訴求一:實時感知連接生命周期的能力,包括連接建立、連接斷開事件。
訴求二:服務端往客戶端主動進行數據推送,需要客戶端進行ACK返回以支持可靠推送,并且需要進行失敗重試。
訴求三:服務端主動推送負載調節能力。
二.性能要求
性能方面,需要能夠滿足阿里的生產環境可用性要求,能支持百萬級的長連接規模及請求量和推送量,并且要保證足夠穩定。
三.負載均衡
說明一:常見的負載均衡策略
隨機、Hash、輪詢、權重、最小連接數、最快響應速度等。
說明二:短連接和長連接負載均衡的異同
由于短連接會快速建立和銷毀,所以"隨機、Hash、輪詢、權重"四種策略下服務端重啟也不會影響均衡,而"最小連接數、最快響應速度",如果數據延時則容易造成堆積問題。
長連接建立連接后,如果沒有異常情況,連接會?直保持。如果出現斷連,需要重新選擇?個新的服務節點。當服務節點出現重啟后,最終會出現連接不均衡的情況。"隨機、輪詢、權重"的策略在客戶端重連切換時可以使用。"最小連接數、最快響應速度",同樣會出現數據延時造成堆積問題。
說明三:長連接和短連接的差別
在于在整體連接穩定時,服務端需要?個重平衡的機制。將集群視角的連接數重新洗牌分配,趨向另外?種穩態。
說明四:客戶端隨機 + 服務端柔性調整
核心的策略是客戶端 + 服務端雙向調節策略,也就是客戶端隨機選擇?+?服務端運行時柔性調整。
說明五:什么是客戶端隨機選擇
客戶端在啟動時獲取服務列表,按照隨機規則進行節點選擇。邏輯比較簡單,整體能夠保持隨機。
說明六:什么是服務端柔性調整
(當前實現版本)人工管控方案:集群視角的系統負載控制臺,提供連接數、負載等視圖,實現人工調節每個Server節點的連接數,人工觸發重平衡,人工削峰填谷。比如展示總節點數量,總長鏈接數量、平均數量、系統負載信息、每個節點的地址、長鏈接數量、與平均數量的差值、對高于平均值的節點進行數量調控、設置數量上限(臨時和持久化),并且可以指定服務節點進行切換。
(未來終態版本)自動化管控方案:基于每個Server間連接數及負載自動計算節點合理連接數,自動觸發Reblance、自動削峰填谷,實現周期長,比較依賴算法準確性。
四.連接生命周期
Nacos需要什么:
第一:低成本快速感知
客戶端要在服務端不可用時盡快切換到新的服務節點,降低不可用時間。并且客戶端要能夠感知底層連接切換事件,重置上下文。服務端需要在客戶端斷開連接時剔除客戶端連接對應的上下文,包括配置監聽、服務訂閱上下文,且處理客戶端連接對應的實例上下線。客戶端正常重啟:客戶端主動關閉連接,服務端實時感知。服務端正常重啟:服務端主動關閉連接,客戶端實時感知。
第二:防抖
當網絡短暫不可用時,客戶端需要能接受短暫網絡抖動。超過閾值后,需要自動切換Server,但要防止請求風暴。
第三:斷網演練
斷網場景下以合理的頻率進行重試,斷網結束時可以快速重連恢復。
(4)長連接選型對比
在上述的備選框架中:從功能的契合度上,RSocket比較貼切Nacos的功能性訴求。RSocket性能上比gRPC要強,社區活躍度相對gRPC要遜色很多。
7.Nacos內核設計之尋址機制
(1)尋址機制的設計抽象
(2)當前Nacos內部實現的幾種尋址機制
(1)尋址機制的設計抽象
Nacos支持單機部署以及集群部署。對于單機模式,Nacos只是自己和自己通信。對于集群模式,則集群內的每個Nacos節點都需要相互通信。因此這就帶來?個問題,應該如何管理集群內的Nacos節點信息。而這,就需要Nacos內部的尋址機制。
單機模式和集群模式的根本區別是Nacos成員節點個數是單個還是多個,而Nacos集群需要能感知節點變更情況:節點是增加了還是減少了。當前最新的成員列表信息是什么,以何種方式去管理成員列表信息,如何快速的支持新的、更優秀的成員列表管理模式等等。針對上述需求點,Nacos抽象出了?個MemberLookup接口如下:
public?interface?MemberLookup?{?//start.void?start() throws NacosException;//Inject the ServerMemberManager property.void?injectMemberManager(ServerMemberManager memberManager);//The addressing pattern finds cluster nodes.void?afterLookup(Collection<Member> members);//Addressing mode closed.void?destroy() throws NacosException;
}
ServerMemberManager存儲著本節點所知道的所有成員節點信息、提供了針對成員節點的增刪改查操作、維護了?個MemberLookup列表。
MemberLookup接口的核心方法是:injectMemberManager()和afterLookup()。前者的作用是將ServerMemberManager注入到MemberLookup中,以方便利用ServerMemberManager 的存儲、查詢能力。后者則是?個事件接口,當MemberLookup需要進行成員節點信息更新時,會將當前最新的成員節點信息通知給ServerMemberManager,具體的節點管理方式,則是隱藏到具體的MemberLookup實現中。
(2)當前Nacos內部實現的幾種尋址機制
一.單機尋址
二.文件尋址
三.地址服務器尋址
四.未來擴展點之集群節點自動擴容縮容
一.單機尋址
com.alibaba.nacos.core.cluster.lookup.StandaloneMemberLookup
單機模式的尋址模式很簡單,其實就是找到自己的IP:PORT組合信息,然后格式化為?個節點信息,接著調用afterLookup()將信息存儲到ServerMemberManager中。
public?class?StandaloneMemberLookup?extends?AbstractMemberLookup?{@Overridepublic?void?start() {if?(start.compareAndSet(false,?true)) {String?url =?InetUtils.getSelfIp() +?":"?+?ApplicationUtils.getPort();afterLookup(MemberUtils.readServerConf(Collections.singletonList(url)));}}?
}
二.文件尋址
com.alibaba.nacos.core.cluster.lookup.FileConfigMemberLookup
文件尋址模式是Nacos集群模式下的默認尋址實現。文件尋址模式也很簡單,就是每個Nacos節點需要維護?個叫cluster.conf的文件。
該文件默認只需填寫每個成員節點的IP信息即可,端口會自動選擇Nacos的默認端口8848。如果有特殊需求更改了Nacos的端口信息,則需要在該文件將該節點的完整網路地址信息補充完整(IP:PORT)。
//cluster.conf文件內容如下:192.168.16.101:8847?
192.168.16.102
192.168.16.103
當Nacos節點啟動時,會讀取cluster.conf文件的內容,然后將文件內的IP解析為節點列表,接著調用afterLookup()方法將節點列表存入ServerMemberManager。
private?void?readClusterConfFromDisk() {Collection<Member> tmpMembers =?new?ArrayList<>();try?{List<String> tmp =?ApplicationUtils.readClusterConf();tmpMembers =?MemberUtils.readServerConf(tmp);}?catch?(Throwable?e) {Loggers.CLUSTER.error("nacos-XXXX[serverlist] failed to get serverlist from disk!, error : {}", e.getMessage());}afterLookup(tmpMembers);?
}
如果集群擴容縮容,那么就要修改每個Nacos節點下的cluster.conf文件,然后Nacos內部的文件變動監聽中心會自動發現文件修改,接著重新讀取文件內容、加載IP列表信息、更新新增的節點。
private?FileWatcher?watcher =?new?FileWatcher() {@Overridepublic?void?onChange(FileChangeEvent event) {readClusterConfFromDisk();}@Overridepublic?boolean?interest(String?context) {return?StringUtils.contains(context,?"cluster.conf");}
};public?void?start() throws?NacosException?{if?(start.compareAndSet(false,?true)) {readClusterConfFromDisk();// Use the inotify mechanism to monitor file changes and automatically trigger the reading of cluster.conftry?{WatchFileCenter.registerWatcher(ApplicationUtils.getConfFilePath(), watcher);}?catch?(Throwable?e) {Loggers.CLUSTER.error("An exception occurred in the launch file monitor : {}", e.getMessage());}}
}
但是這種默認尋址模式有?個缺點——運維成本較大。當新增?個Nacos節點時,需要手動修改每個節點下的cluster.conf文件。而且由于每個Nacos節點都存在?份cluster.conf文件,如果其中?個節點的 cluster.conf 文件修改失敗,就容易造成了集群間成員節點列表數據的不?致性。因此,Nacos引入了新的尋址模式——地址服務器尋址模式。
三.地址服務器尋址
com.alibaba.nacos.core.cluster.lookup.AddressServerMemberLookup
地址服務器尋址模式是Nacos官方推薦的?種集群成員節點信息管理。該模式利用了?個Web服務器來管理cluster.conf文件的內容信息,這樣運維人員只需要管理這?份集群成員節點內容即可。
每個Nacos節點,只需要定時向Web請求當前最新的集群成員節點列表信息。通過地址服務器這種模式,就可以大大簡化Nacos集群節點管理的成本。同時,地址服務器是?個非常簡單的Web程序,其穩定性能得到很好保障。
四.未來擴展點之集群節點自動擴容縮容
目前Nacos的集群節點管理,還屬于人工操作。因此,未來期望能夠基于尋址模式,實現集群節點自動管理的功能。
能夠實現新節點上線時,只需要知道原有集群中的?個節點信息。這樣就可以在?定時間內,順利加入原有的Nacos集群中。同時也能夠自行發現不存活的節點,自動將其從集群可用節點列表中剔除。這其中的邏輯實現,其實就類似Consul的Gossip協議。
8.服務注冊發現模塊的注冊中心的設計原理
(1)服務注冊發現簡介
(2)數據模型
(3)數據一致性
(4)負載均衡
(5)健康檢查
(6)性能與容量
(7)易用性
(8)集群擴展性
(9)用戶擴展性
(1)服務注冊發現簡介
服務注冊發現是?個古老的話題,當應用開始脫離單機運行和訪問時,服務注冊發現就誕生了。
目前的網絡架構是每個主機都有?個獨立的IP地址,那么服務注冊發現基本上都是通過某種方式獲取到服務所部署的IP地址。
DNS協議是最早將?個網絡名稱翻譯為網絡IP的協議。在最初的架構選型中,DNS + LVS + Nginx基本可滿足所有服務的發現,此時服務的IP列表通常配置在Nginx或LVS。
后來出現了RPC服務,服務的上下線更加頻繁,人們開始尋求?種能支持動態上下線?+?推送IP列表變化的注冊中心產品。
互聯網軟件行業普遍熱捧開源產品,個人開發者或中小型公司往往會將開源產品作為選型首選。Zookeeper是?款經典的服務注冊中心產品(雖然它最初定位并不在于此),在很長?段時間里,它是提起RPC服務注冊中心時想到的唯?選擇,這很大程度上與Dubbo在的普及程度有關。
Consul和Eureka都出現于2014年。Consul在設計上把很多分布式服務治理上要用到的功能都包含在內,可以支持服務注冊、健康檢查、配置管理、Service Mesh等。Eureka借著微服務的流行,與SpringCloud生態深度結合,也曾流行起來。
Nacos則攜帶著阿里巴巴大規模服務生產經驗,試圖在服務注冊和配置管理這個市場上,提供給用戶?個新的選擇。
(2)數據模型
一.服務注冊中心數據模型的演進
二.Zookeeper、Eureka、Consul和Nacos的數據模型對比
三.服務實例數據的隔離模型
四.Nacos的臨時服務實例和持久化服務實例
一.服務注冊中心數據模型的演進
服務注冊中心的核心數據是服務的名字和它對應的網絡地址。
當服務注冊了多個實例時,需要對不健康的實例進行過濾或針對實例的?些特征進行流量的分配,那么就要在服務實例上存儲?些如健康狀態、權重等屬性。
隨著服務規模的擴大,漸漸的又需要在整個服務級別設定?些權限規則,以及對所有實例都生效的?些開關,于是在服務級別又會設立?些屬性。
再往后,又發現單個服務的實例又會有劃分為多個子集的需求。例如?個服務是多機房部署的,那么可能要對每個機房的實例做不同配置,這樣又需要在服務和服務實例之間再設定?個數據級別。
二.Zookeeper、Eureka、Consul和Nacos的數據模型對比
Zookeeper沒有針對服務發現設計數據模型,它的數據以?種抽象的樹形KV結構來組織,理論上可存儲任何語義數據。
Eureka或Consul都設計了服務實例級別的數據模型,可滿足大部分場景,不過無法滿足大規模和多環境的服務數據存儲。
Nacos經過多年經驗提煉出的數據模型是?種服務-集群-實例的三層模型,這樣基本可以滿足服務在所有場景下的數據存儲和管理。
Nacos的數據模型雖然相對復雜,但是它并不強制使用它里面的所有數據。在大多數場景下,可以選擇忽略這些數據屬性,此時可以降維成和Eureka或Consul?樣的數據模型。
三.服務實例數據的隔離模型
另外?個需要考慮的是數據的隔離模型。作為?個共享服務型的組件,需要能夠在多個用戶或業務方使用的情況下,保證數據的隔離和安全,這在稍微大?點的業務場景中非常常見。
此外服務注冊中心往往支持云上部署,此時就要求服務注冊中心的數據模型能夠適配云上的通用模型。
Zookeeper、Consul和Eureka都沒有很明確針對服務隔離的模型。Nacos則在?開始就考慮到如何讓用戶能以多種維度進行數據隔離,同時能夠平滑的遷移到阿里云上對應的商業化產品。
Nacos提供了四層的數據邏輯隔離模型。
第一層:用戶賬號
用戶賬號對應的是?個企業或個體,該數據?般不會透傳到服務注冊中心。
第二層:命名空間
?個用戶賬號可以新建多個命名空間。這個命名空間對應的注冊中心物理集群可以根據規則進行路由,這樣可以讓注冊中心內部的升級和遷移對用戶是無感知的。同時可以根據用戶的級別,為用戶提供不同服務級別的物理集群。
第三層:服務分組
服務分組和服務名組成的二維服務標識,通過二維服務標識可以滿足接口級別的服務隔離。
第四層:服務實例名稱
每個命名空間會對應?個服務實例名稱。
四.Nacos的臨時服務實例和持久化服務實例
Nacos 1.0.0介紹的另外?個新特性是:臨時服務實例和持久化服務實例。在定義上區分臨時服務實例和持久化服務實例的關鍵是健康檢查的方式。
臨時服務實例使用客戶端上報模式,持久化服務實例使用服務端反向探測模式。
臨時服務實例需要能夠自動摘除不健康實例,而且無需持久化存儲服務實例,那么這種服務實例就適用于類Gossip的協議。
持久化服務實例使用服務端探測的健康檢查方式。因為客戶端不會上報心跳,那么就不能自動摘除下線的實例,所以這種實例就適用于類Raft的協議。
在大中型的公司里,這兩種類型的服務往往都有。
類型一:?些基礎的組件例如數據庫、緩存等,這些往往不能上報心跳。這種類型的服務在注冊時,就需要作為持久化實例注冊。
類型二:一些上層的業務服務例如微服務、Dubbo服務,服務提供者支持添加匯報心跳的邏輯,此時就可作為臨時實例注冊。
Nacos 2.0中繼續沿用了持久化及非持久化的設定,但有了?些調整。Nacos 1.0中持久化及非持久化屬性是作為實例的元數據進行存儲和識別,這導致了同?個服務下可同時存在持久化實例和非持久化實例。但在實際使用中,這種模式會給運維人員帶來極大的困惑和運維復雜度。從系統架構看,?個服務同時存在持久化及非持久化實例是矛盾的,這就導致該能力事實上并未被廣泛使用。
為了簡化Nacos的服務數據模型,降低運維復雜度,提升Nacos的易用性,在Nacos2.0中已將是否持久化的數據,抽象至服務級別,而且不再允許?個服務同時存在持久化實例和非持久化實例,實例的持久化屬性繼承自服務的持久化屬性。
(3)數據一致性
一.不同的服務注冊場景使用不同的一致性協議
二.Nacos支持AP和CP兩種?致性協議并存
一.不同的服務注冊場景使用不同的一致性協議
數據?致性是分布式系統永恒的話題,Paxos協議的復雜更讓數據?致性成為大牛們吹水的常見話題。
不過從協議層面上看,?致性的選型已經很長時間沒有新的成員加入了。目前來看基本可以歸為兩種:?種是基于Leader的非對等部署的單點寫?致性,?種是對等部署的多點寫?致性。
當我們選用服務注冊中心的時候,并沒有?種協議能夠覆蓋所有場景。
場景一:服務節點不會定時發送心跳到注冊中心
由于無法通過心跳來進行數據的補償注冊,所以第?次注冊就必須保證數據不會丟失,從而讓強?致協議看起來是唯?選擇。
場景二:客戶端會定時發送心跳來匯報健康狀態
第?次注冊的成功率并不是非常關鍵,當然也很關鍵,只是相對來說可以容忍數據少量的寫失敗,因為后續還可以通過心跳再把數據補償回來。此時Paxos協議的單點瓶頸就不太劃算,這也是Eureka為什么不采用Paxos協議而采用自定義的Renew機制的原因。可見不同的服務注冊需求,會用不同的協議。
根據Dubbo對Zookeeper的處理,其實采用Eureka的Renew機制更合適。因為Dubbo服務往Zookeeper注冊的就是臨時節點,Dubbo服務需要定時發送心跳到Zookeeper來續約節點。當Dubbo服務下線時,需要將Zookeeper上的節點摘除。
二.Nacos支持AP和CP兩種?致性協議并存
Zookeeper雖然用ZAB協議保證了數據的強?致,但它缺乏機房容災能力,無法適應?些大型場景。
Nacos因為要支持多種服務類型的注冊,并能夠具有機房容災、集群擴展等必不可少的能力,所以在1.0.0起正式支持AP和CP兩種?致性協議并存。
Nacos1.0.0重構了數據的讀寫和同步邏輯:首先將與業務相關的CRUD與底層的?致性同步邏輯進行了分層隔離。然后將業務的讀寫(主要是寫,因為讀會直接使用業務層的緩存)抽象為Nacos定義的數據類型。接著調用?致性服務進行數據同步。在決定使用CP還是AP?致性時,通過代理 + 可控制的規則進行轉發。
Nacos目前的?致性協議實現:?個是基于簡化的Raft協議的CP?致性,?個是基于自研的Distro協議的AP?致性。
Raft協議,基于Leader進行寫入,其CP也并不是嚴格的。只是能保證?半所見?致,以及數據丟失的概率較小而已。
Distro協議,則是參考了ConfigServer和開源Eureka。在不借助第三方存儲的情況下,實現基本大同小異。Distro重點做了?些邏輯優化和性能調優。
(4)負載均衡
一.客戶端進行負載均衡
二.服務端進行負載均衡
三.服務端負載均衡和客戶端負載均衡的優缺點
四.Ribbon設計的客戶端負載均衡
五.基于標簽的負載均衡策略
六.其他
一.客戶端進行負載均衡
嚴格來說,負載均衡并不算是傳統注冊中心的功能。
?般服務發現的完整流程應該是:客戶端先從注冊中心獲取到服務的實例列表,然后根據自身需求來選擇其中的部分實例,或者按?定的流量分配機制(負載均衡機制)來選擇訪問不同的服務提供者。
因此服務注冊中心本身?般不限定服務消費者的訪問策略。Eureka、Zookeeper、Consul,本身都沒有實現可配置及可擴展的負載均衡機制。Eureka的負載均衡是由Ribbon來完成的,而Consul的負載均衡則是由Fabio來完成。
二.服務端進行負載均衡
在阿里內部,卻是使用的相反的思路。服務消費者往往并不關心所訪問的服務提供者的負載均衡,它們只關心如何高效和正確地訪問服務提供者的服務。服務提供者則非常關注自身被訪問的流量的調配。這其中的一個原因是,阿里內部服務訪問流量巨大,稍有不慎就會導致流量異常壓垮服務提供者的服務。因此服務提供者需要能夠完全掌控服務的流量調配,并可以動態調整。
三.服務端負載均衡和客戶端負載均衡的優缺點
服務端的負載均衡,給服務提供者更強的流量控制權,但是無法滿足不同消費者希望使用不同負載均衡策略的需求,而不同負載均衡策略的場景卻是存在的。
客戶端的負載均衡則提供了這種靈活性,并對用戶擴展提供更友好的支持。但客戶端負載均衡策略如果配置不當,可能會導致服務提供者出現熱點,或者壓根就拿不到任何服務提供者。
拋開負載均衡到底是在服務提供者實現還是在服務消費者實現,目前的負載均衡有基于權重、服務提供者負載、響應時間、標簽等策略。
四.Ribbon設計的客戶端負載均衡
Ribbon設計的客戶端負載均衡,主要是選擇合適現有的IRule、ServerListFilter等接口實現,或者自己繼承這些接口,實現自己的過濾邏輯。
Ribbon設計的客戶端負載均衡,采用的是兩步負載均衡:第?步先過濾掉不會采用的服務提供者實例,第二步在過濾后的服務提供者實例里實施負載均衡策略。
Ribbon內置的幾種負載均衡策略功能還是比較強大的,同時又因為允許用戶去擴展,這可以說是?種比較好的設計。
五.基于標簽的負載均衡策略
基于標簽的負載均衡策略可以做到非常靈活。Kubernetes和Fabio都已經將標簽運用到了對資源的過濾中。使用標簽幾乎可以實現任意比例和權重的服務流量調配。但是標簽本身需要單獨的存儲以及讀寫功能,不管是放在注冊中心本身或者對接第三方的CMDB。
Nacos 0.7.0版本除了提供基于健康檢查和權重的負載均衡方式外,還新提供了基于第三方CMDB的標簽負載均衡器。
使用基于標簽的負載均衡器,可實現同標簽優先訪問的流量調度策略。在實際應用場景中,可用來實現服務的就近訪問。當服務部署在多個地域時,這非常有用。使用這個標簽負載均衡器,可以支持非常多的場景。雖然目前Nacos支持的標簽表達式不豐富,不過會逐步擴展它支持的語法。
六.其他
除此之外,Nacos定義了Selector作為負載均衡的統?抽象。
理想的負載均衡實現應該是什么樣的呢?不同人會有不同答案。Nacos試圖將服務端負載均衡與客戶端負載均衡通過某種機制結合起來,提供用戶擴展性,并給予用戶充分的自主選擇權和輕便的使用方式。
負載均衡是?個很大的話題,當我們關注注冊中心提供的負載均衡策略時,需要注意該注冊中心是否有我們需要的負載均衡方式,使用方式是否復雜。如果沒有,那么是否允許我們方便地擴展來實現我們需求的負載均衡策略。
(5)健康檢查
一.客戶端進行健康檢查—TTL機制發送心跳
二.服務端進行健康檢查—TCP端口和HTTP接口探測
三.客戶端健康檢查和服務端健康檢查的關注點
一.客戶端進行健康檢查—TTL機制發送心跳
Zookeeper和Eureka都實現了?種TTL機制。即如果客戶端在?定時間內沒有向注冊中心發送心跳,則會摘除該客戶端。
Eureka做的更好的?點在于:它允許在注冊服務時,自定義檢查自身狀態的健康檢查方法。這在服務實例能夠保持心跳上報的場景下,有比較好的體驗。
Nacos也支持這種TTL機制,不過這與ConfigServer的機制又有?些區別。Nacos目前支持臨時服務實例使用心跳上報方式維持活性。臨時服務實例發送心跳的周期默認是5秒。如果Nacos服務端在15秒沒收到心跳,則會將該臨時實例設置為不健康。如果Nacos服務端在30秒沒收到心跳,則會將該臨時實例摘除。
不過有?些服務是無法上報心跳的,但可提供?個檢測接口由外部去探測。這樣的服務廣泛存在,且這些服務對服務發現和負載均衡的需求同樣強烈。
二.服務端進行健康檢查—TCP端口和HTTP接口探測
服務端健康檢查最常見的方式是TCP端口探測和HTTP接口返回碼探測,這兩種探測方式因為其協議的通用性可以支持絕大多數的健康檢查場景。
在其他?些特殊場景,可能還需要執行特殊的接口才能判斷服務是否可用。例如部署了數據庫的主備,數據庫的主備可能會在某些情況下進行切換,需要通過服務名對外提供訪問,保證當前訪問的庫是主庫。此時的健康檢查接口,可能是?個檢查數據庫是否是主庫的MySQL命令。
三.客戶端健康檢查和服務端健康檢查的關注點
客戶端健康檢查和服務端健康檢查有不同的關注點。
客戶端健康檢查主要關注:客戶端上報心跳的方式、服務端摘除不健康客戶端的機制。
服務端健康檢查主要關注:探測客戶端的方式、靈敏度以及設置客戶端健康狀態的機制。
從實現復雜性來說,服務端探測肯定是要更加復雜的。因為服務端需要根據注冊服務所配置的健康檢查方式,去執行相應的接口、判斷相應的返回結果、并做好重試機制和線程池管理。這與客戶端探測,只需要等待心跳,然后刷新TTL是不?樣的。
同時服務端健康檢查無法摘除不健康的實例。這意味著注冊過的服務實例,如果不調用接口向服務端主動進行注銷,那么這些服務實例都需要服務端去維持執行健康檢查的探測任務。而客戶端健康檢查則可以隨時摘除不健康實例,減輕服務端的壓力。
Nacos既支持客戶端的健康檢查,也支持服務端的健康檢查。同?個服務可以切換健康檢查模式。這種健康檢查方式的多樣性很重要,這樣可以支持各種類型的服務,并且讓這些服務都可以使用Nacos的負載均衡能力。
Nacos下?步要做的是實現健康檢查方式的用戶擴展機制,不管是服務端探測還是客戶端探測。這樣可以支持用戶傳入?條業務語義的請求,然后由Nacos去執行,做到健康檢查的定制。
(6)性能與容量
一.影響性能的因素
二.Zookeeper的性能及其限制分析
三.Zookeeper、Eureka和Nacos的容量分析
一.影響性能的因素
影響讀寫性能的因素有:?致性協議、機器配置、集群規模、數據規模、數據結構、讀寫邏輯等。
在服務發現的場景中,讀寫性能是非常關鍵的,但是并非性能越高就越好,因為追求性能往往需要其他方面做出犧牲。
二.Zookeeper的性能及其限制分析
Zookeeper的寫性能可以達上萬TPS,這得益于Zookeeper精巧的設計,不過這顯然是因為有?系列的前提存在。
首先Zookeeper的寫邏輯就是進行KV寫入的,內部沒有聚合。其次Zookeeper舍棄了服務發現的基本功能如健康檢查、友好的查詢接口。它在支持這些功能時,顯然需要增加?些邏輯,甚至棄用現有的數據結構。最后Paxos協議本身就限制了Zookeeper集群的規模,3或5個Zookeeper節點是不能應對大規模的服務訂閱和查詢的。
三.Zookeeper、Eureka和Nacos的容量分析
在對容量的評估時,不僅要評估現有的服務規模,也要預測未來3到5年的擴展規模。阿里的中間件在內部支撐著集團百萬級別服務實例,在容量上遇到的挑戰可以說不會小于任何互聯網公司,這個容量不僅僅意味著整體注冊的實例數,也同時包含單個服務的實例數、整體的訂閱者的數目以及查詢QPS等。
Nacos在淘汰Zookeeper和Eureka的過程中,容量是?個非常重要的因素。Zookeeper的容量,從存儲實例數來說,可以達到百萬級別。
但是隨著容量的擴大,性能問題也會隨之而來。當大量的實例上下線時,Zookeeper的表現并不穩定。同時在推送機制上的缺陷,會導致客戶端的資源占用上升,從而導致客戶端性能急劇下降。
Eureka在服務實例規模在5000左右時,就已經出現服務不可用的問題。甚至在壓測過程中,如果并發線程數過高,就會造成Eureka崩潰。不過如果服務實例規模在1000上下,目前所有注冊中心都可滿足。
Nacos可以支撐的服務實例規模約為100萬,服務規模可達10萬+。在實際環境中,這個數字還會因為機器、網絡配置、JVM參數而有所差別。下圖展示了使用Nacos?1.0.0版本進行壓測后的結果總結,針對容量、并發、擴展性和延時等進行了測試和統計。
(7)易用性
易用性包括多方面的工作。比如API和客戶端接入是否簡單,文檔是否齊全,控制臺界面是否完善等。對于開源產品來說,還有?塊是社區是否活躍。
從使用的經驗和調研來看:Zookeeper的易用性是比較差的,其客戶端的使用也比較復雜。而且沒有針對服務發現的模型設計及相應的API封裝,需要自己處理。對多語言的支持也不太好,同時沒有比較好用的控制臺進行運維管理。
Eureka和Nacos相比較Zookeeper而言,已經改善很多。都有針對服務注冊與發現的客戶端,及基于Spring Cloud體系的Starter,可以幫助用戶以非常低的成本無感知的做到服務注冊與發現。同時還暴露標準的HTTP接口,支持多語言和跨平臺訪問。
Eureka和Nacos都提供官方的控制臺來查詢服務注冊情況,不過Eureka 2.0已停止開發,而Nacos目前依然在建設中。
(8)集群擴展性
一.集群擴展性與集群容量及讀寫性能的關系
當使用?個較小的集群規模就能支撐遠高于現有數量的服務注冊及訪問時,集群的擴展能力暫時就不會那么重要。
從協議的層面上來說:Zookeeper使用的ZAB協議,由于是單點寫,在集群擴展性上不具備優勢。Eureka在協議上來說理論上可以擴展到很大規模,但因為都是點對點的數據同步,Eureka集群在擴容后,性能上有很大問題。
二.集群擴展性與多地域部署和容災支持的關系
集群擴展一:雙機房容災
如果不對基于Leader寫的協議進行改造,那么是無法支持雙機房容災的。這意味著Zookeeper不能在沒有人工干預的情況下做到雙機房容災。
在單機房斷網情況下,使機房內服務可用并不難,難的是如何在斷網恢復后做數據聚合。Zookeeper的單點寫模式就會有斷網恢復后的數據對賬問題。
Eureka的部署模式天然支持多機房容災。因為Eureka采用的是純臨時實例的注冊模式:不持久化、所有數據都可以通過客戶端心跳上報進行補償。
由于臨時實例和持久化實例都有它的應用場景,為了能夠兼容這兩種場景,Nacos支持兩種模式的部署。?種是和Eureka?樣的AP協議的部署,這種模式只支持臨時實例,可以完美替代當前的Zookeeper、Eureka,并支持機房容災。另?種是支持持久化實例的CP模式,這種情況下不支持雙機房容災。
集群擴展二:異地多活
很多業務組件的異地多活正是依靠服務注冊中心和配置中心來實現的,這其中包含流量的調度和集群訪問規則的修改等。
機房容災是異地多活的?部分,但是要讓業務能夠在訪問服務注冊中心時,動態調整訪問的集群節點,這需要第三方的組件來做路由。
異地多活往往是?個包含所有產品線的總體方案,很難說單個產品是否支持異地多活。
集群擴展三:多數據中心
多數據中心其實也算是異地多活的?部分。Zookeeper和Eureka都沒有給出官方的多數據中心方案,Nacos則提供了采用Nacos-Sync組件來做數據中心之間的數據同步,這意味著每個數據中心的Nacos集群都會有多個數據中心的全量數據。
Nacos-Sync是Nacos生態組件里的重要?環,不僅承擔Nacos集群與Nacos集群之間的數據同步,也承擔與Eureka、Zookeeper、Kubernetes及Consul間的數據同步。
(9)用戶擴展性
在框架的設計中,擴展性是?個重要的設計原則。Spring、Dubbo、Ribbon等框架都在用戶擴展性上做了比較好的設計。這些框架的擴展性往往由面向接口及動態類加載等技術,來運行用戶擴展約定的接口,實現用戶自定義的邏輯。
在Server的設計中,用戶擴展是比較審慎的。因為引入用戶擴展代碼,可能會影響原有Server服務的可用性。同時如果出問題,排查的難度也比較大。
設計良好的SPI是可能的,但由此帶來的穩定性和運維風險需要仔細考慮。在開源軟件中,往往通過直接貢獻代碼的方式來實現用戶擴展。好的擴展會被很多人不停的更新和維護,這也是?種好的開發模式。
Zookeeper和Eureka目前Server端都不支持用戶擴展。?個支持用戶擴展的服務發現產品是CoreDNS,CoreDNS整體架構就是通過插件來串聯起來的,通過將插件代碼以約定的方式放到CoreDNS工程下,重新構建就可以將插件添加到CoreDNS整體功能鏈路的?環中。
那么這樣的擴展性是否是有必要的呢?假如要添加?種新的健康檢查方式,連接數據庫執行?條MySQL命令,通常是在代碼里增加MySQL類型的健康檢查方法、構建、測試然后發布。但如果允許用戶上傳?個jar包放到Server部署目錄下的某個位置,Server就會自動掃描并識別到這張新的健康檢查方式呢?這樣不僅更酷,也讓整個擴展的流程與Server的代碼解耦,變得非常簡單。
所以對于系統的?些功能,如果能通過精心的設計,開放給用戶進行擴展,那么其擴展性是極強的,畢竟增加擴展的支持不會讓原有功能有任何損失。
所有產品都應該盡量支持用戶進行擴展,這需要Server端的SPI機制設計得足夠健壯和容錯。
Nacos已經通過SPI機制開放了對第三方CMDB的擴展支持,后續很快會開放健康檢查及負載均衡等核心功能的用戶擴展,目的就是為了能夠以?種解耦的方式支持用戶各種各樣的需求。