本篇文章給大家分享的是有關服務模型該如何理解,小編覺得挺實用的,因此分享給大家學習,希望大家閱讀完這篇文章后可以有所收獲,話不多說,跟著小編一起來看看吧。
按照目前市場上的主流使用場景,Nacos 被分成了兩塊功能:服務注冊發現(Naming)和配置中心(Config)。在之前的文章中我介紹了 Nacos 配置中心的實現原理,今天這篇文章所介紹的內容則是與 Nacos 服務注冊發現功能相關,來聊一聊 Nacos 的服務模型。
說到服務模型,其實需要區分視角,一是用戶視角,一個內核視角。即 Nacos 用戶視角看到的服務模型和 Nacos 開發者設計的內核模型可能是完全不一樣的,而今天的文章,是站在用戶視角觀察的,旨在探討 Nacos 服務發現的最佳實踐。
一般我在聊注冊中心時,都會以 Zookeeper 為引子,這也是很多人最熟悉的注冊中心。但如果你真的寫過或看過使用 Zookeeper 作為注冊中心的適配代碼,會發現并不是那么容易,再加上注冊中心涉及到的一致性原理,這就導致很多人對注冊中心的第一印象是:這個東西好難! 但歸根到底是因為 Zookeeper 根本不是專門為注冊中心而設計的,其提供的 API 以及內核設計,并沒有預留出「服務模型」的概念,這就使得開發者需要自行設計一個模型,去填補 Zookeeper 和服務發現之間的鴻溝。
微服務架構逐漸深入人心后,Nacos、Consul、Eureka 等注冊中心組件進入大眾的視線??梢园l現,這些”真正“的注冊中心都有各自的「服務模型」,在使用上也更加的方便。
為什么要有「服務模型」?理論上,一個基礎組件可以被塑造成任意的模樣,如果你愿意,一個數據庫也可以被設計成注冊中心,這并不是”夸張“的修辭手法,在阿里還真有人這么干過。那么代價是什么呢?一定會在業務發展到一定體量后遇到瓶頸,一定會遇到某些極端 case 導致其無法正常工作,一定會導致其擴展性低下。正如剛學習數據結構時,同學們常見的一個疑問一樣:為什么棧只能先進后出。不是所有開發都是中間件專家,所以 Nacos 設計了自己的「服務模型」,這雖然限制了使用者的”想象力“,但保障了使用者在正確地使用 Nacos。
花了一定的篇幅介紹 Nacos 為什么需要設計「服務模型」,再來看看實際的 Nacos 模型是個啥,其實沒那么玄乎,一張圖就能表達清楚:
與 Consul、Eureka 設計有別,Nacos 服務發現使用的領域模型是命名空間-分組-服務-集群-實例這樣的多層結構。服務 Service 和實例 Instance 是核心模型,命名空間 Namespace 、分組 Group、集群 Cluster 則是在不同粒度實現了服務的隔離。
為了更好的理解兩個核心模型:Service 和 Instance,我們以 Dubbo 和 SpringCloud 這兩個已經適配了 Nacos 注冊中心的微服務框架為例,介紹下二者是如何映射對應模型的。
Dubbo。將接口三元組(接口名+分組名+版本號)映射為 Service,將實例 IP 和端口號定義為 Instance。一個典型的注冊在 Nacos 中的 Dubbo 服務:providers:com.alibaba.mse.EchoService:1.0.0:DUBBO
Spring Cloud。將應用名映射為 Service,將實例 IP 和端口號定義為 Instance。一個典型的注冊在 Nacos 中的 Spring Cloud 服務:helloApp
下面我們將會更加詳細地闡釋 Nacos 提供的 API 和服務模型之間的關系。
需要部署一個 Nacos Server 用于測試,我這里選擇直接在
https://mse.console.aliyun.com/ 購買一個 MSE 托管的 Nacos,讀者們可以選擇購買 MSE Nacos 或者自行搭建一個 Nacos Server。
MSE Nacos 提供的可視化控制臺,也可以幫助我們更好的理解 Nacos 的服務模型。下文的一些截圖,均來自 MSE Nacos 的商業化控制臺。
先來實現一個最簡單的服務注冊與發現 demo。Nacos 支持從客戶端注冊服務實例和訂閱服務,具體代碼如下:
Properties properties = new Properties(); properties.setProperty(PropertyKeyConst.SERVER_ADDR, "mse-xxxx-p.nacos-ans.mse.aliyuncs.com:8848"); String serviceName = "nacos.test.service.1"; String instanceIp = InetAddress.getLocalHost().getHostAddress(); int instancePort = 8080; namingService.registerInstance(serviceName, instanceIp, instancePort); System.out.println(namingService.getAllInstances(serviceName));
上述代碼定義了一個 service:nacos.test.service.1;定義了一個 instance,以本機 host 為 IP 和 8080 為端口號,觀察實際的注冊情況:
并且控制臺也打印出了服務的詳情。至此一個最簡單的 Nacos 服務發現 demo 就已經完成了。對一些細節稍作解釋:
屬性 PropertyKeyConst.SERVER_ADDR 表示的是 Nacos 服務端的地址。
創建一個 NamingService 實例,客戶端將為該實例創建單獨的資源空間,包括緩存、線程池以及配置等。Nacos 客戶端沒有對該實例做單例的限制,請小心維護這個實例,以防新建多于預期的實例。
注冊服務 registerInstance 使用了最簡單的重載方法,只需要傳入服務名、IP、端口就可以。
上述的例子中,并沒有出現 Namespace、Group、Cluster 等前文提及的服務模型,我會在下面一節詳細介紹,這個例子主要是為了演示 Nacos 支持的一些缺省配置,其中 Service 和 Instance 是必不可少的,這也驗證了前文提到的服務和實例是 Nacos 的一等公民。
通過截圖我們可以發現缺省配置的默認值:
Namespace:默認值是 public 或者空字符串,都可以代表默認命名空間。
Group:默認值是 DEFAULT_GROUP。
Cluster:默認值是 DEFAULT。
為了展現出 Nacos 服務模型的全貌,還需要介紹下實例相關的 API。例如我們希望注冊的實例中,有一些能夠被分配更多的流量;或者能夠傳入一些實例的元信息存儲到 Nacos 服務端,例如 IP 所屬的應用或者所在的機房,這樣在客戶端可以根據服務下掛載的實例元信息,來自定義負載均衡模式。Nacos 也提供了另外的注冊實例接口,使得用戶在注冊實例時可以指定實例的屬性:
/** * register a instance to service with specified instance properties. * * @param serviceName name of service * @param groupName group of service * @param instance instance to register * @throws NacosException nacos exception */ void registerInstance(String serviceName, String groupName, Instance instance) throws NacosException;
這個方法在注冊實例時,可以傳入一個 Instance 實例,它的屬性如下:
public class Instance { /** * unique id of this instance. */ private String instanceId; /** * instance ip. */ private String ip; /** * instance port. */ private int port; /** * instance weight. */ private double weight = 1.0D; /** * instance health status. */ private boolean healthy = true; /** * If instance is enabled to accept request. */ private boolean enabled = true; /** * If instance is ephemeral. * * @since 1.0.0 */ private boolean ephemeral = true; /** * cluster information of instance. */ private String clusterName; /** * Service information of instance. */ private String serviceName; /** * user extended attributes. */ private Map<String, String> metadata = new HashMap<String, String>(); }
有一些字段可以望文生義,有一些則需要花些功夫專門去了解 Nacos 的設計,我這里挑選幾個我認為重要的屬性重點介紹下:
healthy 實例健康狀態。標識該實例是否健康,一般心跳健康檢查會自動更新該字段。
enable 是否啟用。它跟 healthy 區別在于,healthy 一般是由內核健康檢查更新,而 enable 更多是業務語義偏多,可以完全根據業務場景操控。例如在 Dubbo 中,一般使用該字段標識某個實例 IP 的上下線狀態。
ephemeral 臨時實例還是持久化實例。非常關鍵的一個字段,需要對 Nacos 有較為深入的了解才能夠理解該字段的含義。區別在于,心跳檢測失敗一定時間之后,實例是自動下線還是標記為不健康。一般在注冊中心場景下,會使用臨時實例。這樣心跳檢測失敗之后,可以讓消費者及時收到下線通知;而在 DNS 模式下,使用持久化實例較多。在《一文詳解 Nacos 高可用特性》中我也介紹過,該字段還會影響到 Nacos 的一致性協議。
metadata 元數據。一個 map 結構,可以存儲實例的自定義擴展信息,例如機房信息,路由標簽,應用信息,權重信息等。
這些信息在由服務提供者上報之后,由服務消費者獲取,從而完成信息的傳遞。以下是一個完整的實例注冊演示代碼:
Properties properties = new Properties(); // 指定 Nacos Server 地址 properties.setProperty(PropertyKeyConst.SERVER_ADDR, "mse-xxxx-p.nacos-ans.mse.aliyuncs.com:8848"); // 指定命名空間 properties.setProperty(PropertyKeyConst.NAMESPACE, "9125571e-bf50-4260-9be5-18a3b2e3605b"); NamingService namingService = NacosFactory.createNamingService(properties); String serviceName = "nacos.test.service.1"; String group = "DEFAULT_GROUP"; String clusterName = "cn-hangzhou"; String instanceIp = InetAddress.getLocalHost().getHostAddress(); int instancePort = 8080; Instance instance = new Instance(); // 指定集群名 instance.setClusterName(clusterName); instance.setIp(instanceIp); instance.setPort(instancePort); // 指定實例的元數據 Map<String, String> metadata = new HashMap<>(); metadata.put("app", "nacos-demo"); metadata.put("site", "cn-hangzhou"); metadata.put("protocol", "1.3.3"); instance.setMetadata(metadata); // 指定服務名、分組和實例 namingService.registerInstance(serviceName, group, instance); System.out.println(namingService.getAllInstances(serviceName));
除了實例之外,服務也可以自定義配置,Nacos 的服務隨著實例的注冊而存在,并隨著所有實例的注銷而消亡。不過目前 Nacos 對于自定義服務的支持不是很友好,除使用 OpenApi 可以修改服務的屬性外,就只能使用注冊實例時傳入的服務屬性來進行自定義配置。所以在實際的 Dubbo 和 SpringCloud 中,自定義服務一般較少使用,而自定義實例信息則相對常用。
Nacos 的服務與 Consul、Eureka 的模型都不同,Consul 與 Eureka的服務等同于 Nacos 的實例,每個實例有一個服務名屬性,服務本身并不是一個單獨的模型。Nacos 的設計在我看來更為合理,其認為服務本身也是具有數據存儲需求的,例如作用于服務下所有實例的配置、權限控制等。實例的屬性應當繼承自服務的屬性,實例級別可以覆蓋服務級別。以下是服務的數據結構:
/** * Service name */ private String name; /** * Protect threshold */ private float protectThreshold = 0.0F; /** * Application name of this service */ private String app; /** * Service group which is meant to classify services into different sets. */ private String group; /** * Health check mode. */ private String healthCheckMode; private Map<String, String> metadata = new HashMap<String, String>();
在實際使用過程中,可以像快速開始章節中介紹的那樣,僅僅使用 ServiceName 標記一個服務。
出于篇幅考慮,這三個概念放到一起介紹。
襄王有意,神女無心。Nacos 提出了這幾種隔離策略,目前看來只有 Namespace 在實際應用中使用較多,而 Group 和 Cluster 并沒有被當回事。
Cluster 集群隔離在阿里巴巴內部使用的非常普遍。一個典型的場景是這個服務下的實例,需要配置多種健康檢查方式,有一些實例使用 TCP 的健康檢查方式,另外一些使用 HTTP 的健康檢查方式。另一個場景是,服務下掛載的機器分屬不同的環境,希望能夠在某些情況下將某個環境的流量全部切走,這樣可以通過集群隔離,來做到一次性切流。在 Nacos 2.0 中,也在有意的弱化集群的概念,畢竟開源還是要面向用戶的,有些東西適合阿里,但不一定適合開源,等再往后演進,集群這個概念又有可能重新回到大家的視線中了,history will repeat itself。
Group 分組隔離的概念可以參考 Dubbo 的服務隔離策略,其也有一個分組。支持分組的擴展,用意當然是好的,實際使用上,也的確有一些公司會習慣使用分組來進行隔離。需要注意的一點是:Dubbo 注冊三元組(接口名+分組+版本)時,其中 Dubbo 的分組是包含在 Nacos 的服務名中的,并不是映射成了 Nacos 的分組,一般 Nacos 注冊的服務是默認注冊到 DEFAULT_GROUP 分組的。
Namespace 命名空間隔離,我認為是 Nacos 一個比較好的設計。在實際場景中使用也比較普遍,一般用于多個環境的隔離,例如 daily,dev,test,uat,prod 等環境的隔離。特別是當環境非常多時,使用命名空間做邏輯隔離是一個比較節約成本的策略。但強烈建議大家僅僅在非線上環境使用 Namespace 進行隔離,例如多套測試環境可以共享一套 Nacos,而線上環境單獨搭建另一套 Nacos 集群,以免線下測試流量干擾到線上環境。
上面介紹完了 Nacos 服務發現的 5 大領域模型,最后一節,介紹下如何獲取服務模型。
Nacos 的服務發現,有主動拉取和推送兩種模式,這與一般的服務發現架構相同。以下是拉模型的相關接口:
/** * Get all instances of a service * * @param serviceName name of service * @return A list of instance * @throws NacosException */ List<Instance> getAllInstances(String serviceName) throws NacosException; /** * Get qualified instances of service * * @param serviceName name of service * @param healthy a flag to indicate returning healthy or unhealthy instances * @return A qualified list of instance * @throws NacosException */ List<Instance> selectInstances(String serviceName, boolean healthy) throws NacosException; /** * Select one healthy instance of service using predefined load balance strategy * * @param serviceName name of service * @return qualified instance * @throws NacosException */ Instance selectOneHealthyInstance(String serviceName) throws NacosException;
Nacos 提供了三個同步拉取服務的方法,一個是查詢所有注冊的實例,一個是只查詢健康且上線的實例,還有一個是獲取一個健康且上線的實例。一般情況下,訂閱端并不關心不健康的實例或者權重設為 0 的實例,但是也不排除一些場景下,有一些運維或者管理的場景需要拿到所有的實例。細心的讀者會注意到上述 Nacos 實例中有一個 weight 字段,便是作用在此處的selectOneHealthyInstance接口上,按照權重返回一個健康的實例。個人認為這個功能相對雞肋,一般的 RPC 框架都有自身配套的負載均衡策略,很少會由注冊中心 cover,事實上 Dubbo 和 Spring Cloud 都沒有用到 Nacos 的這個接口。
除了主動查詢實例列表,Nacos還提供訂閱模式來感知服務下實例列表的變化,包括服務配置或者實例配置的變化??梢允褂孟旅娴慕涌趤磉M行訂閱或者取消訂閱:
/** * Subscribe service to receive events of instances alteration * * @param serviceName name of service * @param listener event listener * @throws NacosException */ void subscribe(String serviceName, EventListener listener) throws NacosException; /** * Unsubscribe event listener of service * * @param serviceName name of service * @param listener event listener * @throws NacosException */ void unsubscribe(String serviceName, EventListener listener) throws NacosException;
在實際的服務發現中,訂閱接口尤為重要。消費者啟動時,一般會同步獲取一次服務信息用于初始化,緊接著訂閱服務,這樣當服務發生上下線時,就可以感知變化了,從而實現服務發現。
以上就是服務模型該如何理解,小編相信有部分知識點可能是我們日常工作會見到或用到的。希望你能通過這篇文章學到更多知識。更多詳情敬請關注億速云行業資訊頻道。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。