微服務的靈魂擺渡者——Nacos,來一篇原理全攻略
本文轉載自微信公眾號「程序新視界」,作者二師兄。轉載本文請聯系程序新視界公眾號。
前言
Nacos在微服務系統的服務注冊和發現領域,勢頭迅猛是肉眼可見的。在微服務系統中,服務的注冊和發現又是一個靈魂的存在。沒有注冊中心的存在,成百上千服務之間的調用復雜度不可想象。
如果你計劃或已經在使用Nacos了,但僅停留在使用層面,那這篇文章值得你一讀。
本文我們先從服務發現機制說起,然后講解Nacos的基本介紹、實現原理、架構等,真正做到深入淺出的了解Nacos。
服務注冊與發現
說起Nacos,不得不先聊聊微服務架構中的服務發現。關于服務發現其實已經在《要學習微服務的服務發現?先來了解一些科普知識吧》一文中進行了全面的講解。我們這里再簡要梳理一下。
在傳統應用中,一個服務A訪問另外一個服務B,我們只需將服務B的服務地址和端口在服務A的靜態配置文件中進行配置即可。
但在微服務的架構中,這種情況就有所變化了,如下圖所示:
上圖中,服務實例的IP是動態分配。同時,還面臨著服務的增減、故障、升級等變化。這種情況,對于客戶端程序來說,就需要使用更精確的服務發現機制。
為了解決這個問題,于是像etcd、Consul、Apache Zookeeper、Nacos等服務注冊中間件便應運而生。
Nacos簡介
Nacos一般讀作/nɑ:k??s/,這個名字來源于“Dynamic Naming and Configuration Service”。其中na取自“Naming”的前兩個字母,co取自“Configuration”的前兩個字母,而s則取自“Service”的首字母。
Nacos的功能官方用一句話來進行了說明:“一個更易于構建云原生應用的動態服務發現、配置管理和服務管理平臺。”也就是說Nacos不僅提供了服務注冊與發現功能,還提供了配置管理的功能,同時還提供了可視化的管理平臺。
官方文檔中還提到“服務(Service)是Nacos世界的一等公民。”,也就是說在Nacos是圍繞著Service轉的。
如果查看源碼,會發現Nacos的核心API中定義了兩個接口NamingService和ConfigService。服務注冊與發現圍繞著NamingService展開,而配置管理則圍繞著ConfigService展開。
官網給出了Nacos的4個核心特性:服務發現和服務健康監測、動態配置服務、動態DNS服務、服務及其元數據管理。我們主要來講服務發現功能。
Nacos的Server與Client
Nacos注冊中心分為Server與Client,Nacos提供SDK和openApi,如果沒有SDK也可以根據openApi手動寫服務注冊與發現和配置拉取的邏輯。
Server采用Java編寫,基于Spring Boot框架,為Client提供注冊發現服務與配置服務。
Client支持包含了目前已知的Nacos多語言客戶端及Spring生態的相關客戶端。Client與微服務嵌套在一起。
Nacos的DNS實現依賴了CoreDNS,其項目為nacos-coredns-plugin。該插件提供了基于CoreDNS的DNS-F客戶端,開發語言為go。
Nacos注冊中的交互流程
作為注冊中心的功能來說,Nacos提供的功能與其他主流框架很類似,基本都是圍繞服務實例注冊、實例健康檢查、服務實例獲取這三個核心來實現的。
以Java版本的Nacos客戶端為例,服務注冊基本流程:
- 服務實例啟動將自身注冊到Nacos注冊中心,隨后維持與注冊中心的心跳;
- 心跳維持策略為每5秒向Nacos Server發送一次心跳,并攜帶實例信息(服務名、實例IP、端口等);
- Nacos Server也會向Client主動發起健康檢查,支持TCP/Http;
- 15秒內無心跳且健康檢查失敗則認為實例不健康,如果30秒內健康檢查失敗則剔除實例;
- 服務消費者通過注冊中心獲取實例,并發起調用;
其中服務發現支持兩種場景:第一,服務消費者直接向注冊中心發送獲取某服務實例的請求,注冊中心返回所有可用實例,但一般不推薦此種方式;第二、服務消費者向注冊中心訂閱某服務,并提交一個監聽器,當注冊中心中服務發生變化時,監聽器會收到通知,消費者更新本地服務實例列表,以保證所有的服務均可用。
Nacos數據模型
關于數據模型,官網描述道:Nacos數據模型的Key由三元組唯一確定,Namespace默認是空串,公共命名空間(public),分組默認是DEFAULT_GROUP。
上面的圖為官方提供的圖,我們可以進一步細化拆分來看一下:
如果還無法理解,我們可以直接從代碼層面來看看Namespace、Group和Service是如何存儲的:
- /**
- * Map(namespace, Map(group::serviceName, Service)).
- */
- private final Map<String, Map<String, Service>> serviceMap = new ConcurrentHashMap<>();
也就是說Nacos服務注冊表結構為:Map
Nacos基于namespace的設計是為了做多環境以及多租戶數據(配置和服務)隔離的。如果用戶有多套環境(開發、測試、生產等環境),則可以分別建三個不同的namespace,比如上圖中的dev-namespace和prod-namespace。
Nacos服務領域模型
在上面的數據模式中,我們可以定位到一個服務(Service)了,那么服務的模型又是如何呢?官網提供了下圖:
從圖中的分級存儲模型可以看到,在服務級別,保存了健康檢查開關、元數據、路由機制、保護閾值等設置,而集群保存了健康檢查模式、元數據、同步機制等數據,實例保存了該實例的ip、端口、權重、健康檢查狀態、下線狀態、元數據、響應時間。
此時,我們忽略掉一對多的情況,整個Nacos中數據存儲的關系如下圖:
可以看出,整個層級的包含關系為Namespace包含多個Group、Group可包含多個Service、Service可包含多個Cluster、Cluster中包含Instance集合。
對應的部分源碼如下:
- // ServiceManager類,Map(namespace, Map(group::serviceName, Service))
- private final Map<String, Map<String, Service>> serviceMap = new ConcurrentHashMap<>();
- // Service類,Map(cluster,Cluster)
- private Map<String, Cluster> clusterMap = new HashMap<>();
- // Cluster類
- private Set<Instance> persistentInstances = new HashSet<>();
- private Set<Instance> ephemeralInstances = new HashSet<>();
- // Instance類
- private String instanceId;
- private String ip;
- private int port;
- private double weight = 1.0D;
其中,實例又分為臨時實例和持久化實例。它們的區別關鍵是健康檢查的方式。臨時實例使用客戶端上報模式,而持久化實例使用服務端反向探測模式。
臨時實例需要能夠自動摘除不健康實例,而且無需持久化存儲實例。持久化實例使用服務端探測的健康檢查方式,因為客戶端不會上報心跳,自然就不能去自動摘除下線的實例。
小結
我們從微服務系統中為什么使用服務發現講起,然后介紹了Nacos、Nacos的實現機制、底層數據模型以及部分源碼實現。
在使用過程中除了關注服務注冊與發現、健康檢查之外,對于服務的數據模型中Namespace、Group、Service和Instance也需要重點關注。
當理解了這些背后的工作原理,對于上層應用的整合以及配置便可以輕松運用了。