ans(關(guān)于ans的基本詳情介紹)

作者:島風(fēng)

前言

按照目前市場(chǎng)上的主流使用場(chǎng)景,Nacos 被分成了兩塊功能:服務(wù)注冊(cè)發(fā)現(xiàn)(Naming)和配置中心(Config)。在之前的文章中我介紹了 Nacos 配置中心的實(shí)現(xiàn)原理,今天這篇文章所介紹的內(nèi)容則是與 Nacos 服務(wù)注冊(cè)發(fā)現(xiàn)功能相關(guān),來聊一聊 Nacos 的服務(wù)模型。

說到服務(wù)模型,其實(shí)需要區(qū)分視角,一是用戶視角,一個(gè)內(nèi)核視角。即 Nacos 用戶視角看到的服務(wù)模型和 Nacos 開發(fā)者設(shè)計(jì)的內(nèi)核模型可能是完全不一樣的,而今天的文章,是站在用戶視角觀察的,旨在探討 Nacos 服務(wù)發(fā)現(xiàn)的最佳實(shí)踐。

服務(wù)模型介紹

一般我在聊注冊(cè)中心時(shí),都會(huì)以 Zookeeper 為引子,這也是很多人最熟悉的注冊(cè)中心。但如果你真的寫過或看過使用 Zookeeper 作為注冊(cè)中心的適配代碼,會(huì)發(fā)現(xiàn)并不是那么容易,再加上注冊(cè)中心涉及到的一致性原理,這就導(dǎo)致很多人對(duì)注冊(cè)中心的第一印象是:這個(gè)東西好難! 但歸根到底是因?yàn)?Zookeeper 根本不是專門為注冊(cè)中心而設(shè)計(jì)的,其提供的 API 以及內(nèi)核設(shè)計(jì),并沒有預(yù)留出「服務(wù)模型」的概念,這就使得開發(fā)者需要自行設(shè)計(jì)一個(gè)模型,去填補(bǔ) Zookeeper 和服務(wù)發(fā)現(xiàn)之間的鴻溝。

微服務(wù)架構(gòu)逐漸深入人心后,Nacos、Consul、Eureka 等注冊(cè)中心組件進(jìn)入大眾的視線。可以發(fā)現(xiàn),這些真正的注冊(cè)中心都有各自的「服務(wù)模型」,在使用上也更加的方便。

為什么要有「服務(wù)模型」?理論上,一個(gè)基礎(chǔ)組件可以被塑造成任意的模樣,如果你愿意,一個(gè)數(shù)據(jù)庫也可以被設(shè)計(jì)成注冊(cè)中心,這并不是夸張的修辭手法,在阿里還真有人這么干過。那么代價(jià)是什么呢?一定會(huì)在業(yè)務(wù)發(fā)展到一定體量后遇到瓶頸,一定會(huì)遇到某些極端 case 導(dǎo)致其無法正常工作,一定會(huì)導(dǎo)致其擴(kuò)展性低下。正如剛學(xué)習(xí)數(shù)據(jù)結(jié)構(gòu)時(shí),同學(xué)們常見的一個(gè)疑問一樣:為什么棧只能先進(jìn)后出。不是所有開發(fā)都是中間件專家,所以 Nacos 設(shè)計(jì)了自己的「服務(wù)模型」,這雖然限制了使用者的想象力,但保障了使用者在正確地使用 Nacos。

花了一定的篇幅介紹 Nacos 為什么需要設(shè)計(jì)「服務(wù)模型」,再來看看實(shí)際的 Nacos 模型是個(gè)啥,其實(shí)沒那么玄乎,一張圖就能表達(dá)清楚:

與 Consul、Eureka 設(shè)計(jì)有別,Nacos 服務(wù)發(fā)現(xiàn)使用的領(lǐng)域模型是命名空間-分組-服務(wù)-集群-實(shí)例這樣的多層結(jié)構(gòu)。服務(wù) Service 和實(shí)例 Instance 是核心模型,命名空間 Namespace 、分組 Group、集群 Cluster 則是在不同粒度實(shí)現(xiàn)了服務(wù)的隔離。

為了更好的理解兩個(gè)核心模型:Service 和 Instance,我們以 Dubbo 和 SpringCloud 這兩個(gè)已經(jīng)適配了 Nacos 注冊(cè)中心的微服務(wù)框架為例,介紹下二者是如何映射對(duì)應(yīng)模型的。

Dubbo。將接口三元組(接口名+分組名+版本號(hào))映射為 Service,將實(shí)例 IP 和端口號(hào)定義為 Instance。一個(gè)典型的注冊(cè)在 Nacos 中的 Dubbo 服務(wù):providers:com.alibaba.mse.EchoService:1.0.0:DUBBOSpring Cloud。將應(yīng)用名映射為 Service,將實(shí)例 IP 和端口號(hào)定義為 Instance。一個(gè)典型的注冊(cè)在 Nacos 中的 Spring Cloud 服務(wù):helloApp

下面我們將會(huì)更加詳細(xì)地闡釋 Nacos 提供的 API 和服務(wù)模型之間的關(guān)系。

環(huán)境準(zhǔn)備

需要部署一個(gè) Nacos Server 用于測(cè)試,我這里選擇直接在https://mse.console.aliyun.com/ 購(gòu)買一個(gè) MSE 托管的 Nacos,讀者們可以選擇購(gòu)買 MSE Nacos 或者自行搭建一個(gè) Nacos Server。

MSE Nacos 提供的可視化控制臺(tái),也可以幫助我們更好的理解 Nacos 的服務(wù)模型。下文的一些截圖,均來自 MSE Nacos 的商業(yè)化控制臺(tái)。

快速開始

先來實(shí)現(xiàn)一個(gè)最簡(jiǎn)單的服務(wù)注冊(cè)與發(fā)現(xiàn) demo。Nacos 支持從客戶端注冊(cè)服務(wù)實(shí)例和訂閱服務(wù),具體代碼如下:

Properties properties =newProperties(); 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();intinstancePort =8080; namingService.registerInstance(serviceName, instanceIp, instancePort); System.out.println(namingService.getAllInstances(serviceName));

上述代碼定義了一個(gè) service:nacos.test.service.1;定義了一個(gè) instance,以本機(jī) host 為 IP 和 8080 為端口號(hào),觀察實(shí)際的注冊(cè)情況:

并且控制臺(tái)也打印出了服務(wù)的詳情。至此一個(gè)最簡(jiǎn)單的 Nacos 服務(wù)發(fā)現(xiàn) demo 就已經(jīng)完成了。對(duì)一些細(xì)節(jié)稍作解釋:

屬性 PropertyKeyConst.SERVER_ADDR 表示的是 Nacos 服務(wù)端的地址。創(chuàng)建一個(gè) NamingService 實(shí)例,客戶端將為該實(shí)例創(chuàng)建單獨(dú)的資源空間,包括緩存、線程池以及配置等。Nacos 客戶端沒有對(duì)該實(shí)例做單例的限制,請(qǐng)小心維護(hù)這個(gè)實(shí)例,以防新建多于預(yù)期的實(shí)例。注冊(cè)服務(wù) registerInstance 使用了最簡(jiǎn)單的重載方法,只需要傳入服務(wù)名、IP、端口就可以。

上述的例子中,并沒有出現(xiàn) Namespace、Group、Cluster 等前文提及的服務(wù)模型,我會(huì)在下面一節(jié)詳細(xì)介紹,這個(gè)例子主要是為了演示 Nacos 支持的一些缺省配置,其中 Service 和 Instance 是必不可少的,這也驗(yàn)證了前文提到的服務(wù)和實(shí)例是 Nacos 的一等公民。

通過截圖我們可以發(fā)現(xiàn)缺省配置的默認(rèn)值:

Namespace:默認(rèn)值是 public 或者空字符串,都可以代表默認(rèn)命名空間。Group:默認(rèn)值是 DEFAULT_GROUP。Cluster:默認(rèn)值是 DEFAULT。

構(gòu)建自定義實(shí)例

為了展現(xiàn)出 Nacos 服務(wù)模型的全貌,還需要介紹下實(shí)例相關(guān)的 API。例如我們希望注冊(cè)的實(shí)例中,有一些能夠被分配更多的流量;或者能夠傳入一些實(shí)例的元信息存儲(chǔ)到 Nacos 服務(wù)端,例如 IP 所屬的應(yīng)用或者所在的機(jī)房,這樣在客戶端可以根據(jù)服務(wù)下掛載的實(shí)例元信息,來自定義負(fù)載均衡模式。Nacos 也提供了另外的注冊(cè)實(shí)例接口,使得用戶在注冊(cè)實(shí)例時(shí)可以指定實(shí)例的屬性:

/** * register a instance to service with specified instance properties. * *@paramserviceName name of service *@paramgroupName group of service *@paraminstance instance to register *@throwsNacosException nacos exception */voidregisterInstance(String serviceName, String groupName, Instance instance)throwsNacosException;

這個(gè)方法在注冊(cè)實(shí)例時(shí),可以傳入一個(gè) Instance 實(shí)例,它的屬性如下:

publicclassInstance {/** * unique id of this instance. */privateStringinstanceId;/** * instance ip. */privateStringip;/** * instance port. */privateint port;/** * instance weight. */privatedouble weight =1.0D;/** * instance health status. */privatebooleanhealthy =true;/** * If instance is enabled to accept request. */privatebooleanenabled =true;/** * If instance is ephemeral. * * @since 1.0.0 */privatebooleanephemeral =true;/** * cluster information of instance. */privateStringclusterName;/** * Service information of instance. */privateStringserviceName;/** * user extended attributes. */privateMap<String,String> metadata =newHashMap<String,String>(); }

有一些字段可以望文生義,有一些則需要花些功夫?qū)iT去了解 Nacos 的設(shè)計(jì),我這里挑選幾個(gè)我認(rèn)為重要的屬性重點(diǎn)介紹下:

healthy 實(shí)例健康狀態(tài)。標(biāo)識(shí)該實(shí)例是否健康,一般心跳健康檢查會(huì)自動(dòng)更新該字段。enable 是否啟用。它跟 healthy 區(qū)別在于,healthy 一般是由內(nèi)核健康檢查更新,而 enable 更多是業(yè)務(wù)語義偏多,可以完全根據(jù)業(yè)務(wù)場(chǎng)景操控。例如在 Dubbo 中,一般使用該字段標(biāo)識(shí)某個(gè)實(shí)例 IP 的上下線狀態(tài)。ephemeral 臨時(shí)實(shí)例還是持久化實(shí)例。非常關(guān)鍵的一個(gè)字段,需要對(duì) Nacos 有較為深入的了解才能夠理解該字段的含義。區(qū)別在于,心跳檢測(cè)失敗一定時(shí)間之后,實(shí)例是自動(dòng)下線還是標(biāo)記為不健康。一般在注冊(cè)中心場(chǎng)景下,會(huì)使用臨時(shí)實(shí)例。這樣心跳檢測(cè)失敗之后,可以讓消費(fèi)者及時(shí)收到下線通知;而在 DNS 模式下,使用持久化實(shí)例較多。在《一文詳解 Nacos 高可用特性》中我也介紹過,該字段還會(huì)影響到 Nacos 的一致性協(xié)議。metadata 元數(shù)據(jù)。一個(gè) map 結(jié)構(gòu),可以存儲(chǔ)實(shí)例的自定義擴(kuò)展信息,例如機(jī)房信息,路由標(biāo)簽,應(yīng)用信息,權(quán)重信息等。

這些信息在由服務(wù)提供者上報(bào)之后,由服務(wù)消費(fèi)者獲取,從而完成信息的傳遞。以下是一個(gè)完整的實(shí)例注冊(cè)演示代碼:

Properties properties =newProperties();// 指定 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);StringserviceName ="nacos.test.service.1";Stringgroup ="DEFAULT_GROUP";StringclusterName ="cn-hangzhou";StringinstanceIp = InetAddress.getLocalHost().getHostAddress(); int instancePort =8080; Instance instance =newInstance();// 指定集群名instance.setClusterName(clusterName); instance.setIp(instanceIp); instance.setPort(instancePort);// 指定實(shí)例的元數(shù)據(jù)Map<String,String> metadata =newHashMap<>(); metadata.put("app","nacos-demo"); metadata.put("site","cn-hangzhou"); metadata.put("protocol","1.3.3"); instance.setMetadata(metadata);// 指定服務(wù)名、分組和實(shí)例namingService.registerInstance(serviceName, group, instance); System.out.println(namingService.getAllInstances(serviceName));

構(gòu)建自定義服務(wù)

除了實(shí)例之外,服務(wù)也可以自定義配置,Nacos 的服務(wù)隨著實(shí)例的注冊(cè)而存在,并隨著所有實(shí)例的注銷而消亡。不過目前 Nacos 對(duì)于自定義服務(wù)的支持不是很友好,除使用 OpenApi 可以修改服務(wù)的屬性外,就只能使用注冊(cè)實(shí)例時(shí)傳入的服務(wù)屬性來進(jìn)行自定義配置。所以在實(shí)際的 Dubbo 和 SpringCloud 中,自定義服務(wù)一般較少使用,而自定義實(shí)例信息則相對(duì)常用。

Nacos 的服務(wù)與 Consul、Eureka 的模型都不同,Consul 與 Eureka的服務(wù)等同于 Nacos 的實(shí)例,每個(gè)實(shí)例有一個(gè)服務(wù)名屬性,服務(wù)本身并不是一個(gè)單獨(dú)的模型。Nacos 的設(shè)計(jì)在我看來更為合理,其認(rèn)為服務(wù)本身也是具有數(shù)據(jù)存儲(chǔ)需求的,例如作用于服務(wù)下所有實(shí)例的配置、權(quán)限控制等。實(shí)例的屬性應(yīng)當(dāng)繼承自服務(wù)的屬性,實(shí)例級(jí)別可以覆蓋服務(wù)級(jí)別。以下是服務(wù)的數(shù)據(jù)結(jié)構(gòu):

/** * Service name */privateStringname;/** * Protect threshold */privatefloat protectThreshold =0.0F;/** * Application name of this service */privateStringapp;/** * Service group which is meant to classify services into different sets. */privateStringgroup;/** * Health check mode. */privateStringhealthCheckMode;privateMap<String,String> metadata =newHashMap<String,String>();

在實(shí)際使用過程中,可以像快速開始章節(jié)中介紹的那樣,僅僅使用 ServiceName 標(biāo)記一個(gè)服務(wù)。

服務(wù)隔離:Namespace&Group&Cluster

出于篇幅考慮,這三個(gè)概念放到一起介紹。

襄王有意,神女無心。Nacos 提出了這幾種隔離策略,目前看來只有 Namespace 在實(shí)際應(yīng)用中使用較多,而 Group 和 Cluster 并沒有被當(dāng)回事。

Cluster 集群隔離在阿里巴巴內(nèi)部使用的非常普遍。一個(gè)典型的場(chǎng)景是這個(gè)服務(wù)下的實(shí)例,需要配置多種健康檢查方式,有一些實(shí)例使用 TCP 的健康檢查方式,另外一些使用 HTTP 的健康檢查方式。另一個(gè)場(chǎng)景是,服務(wù)下掛載的機(jī)器分屬不同的環(huán)境,希望能夠在某些情況下將某個(gè)環(huán)境的流量全部切走,這樣可以通過集群隔離,來做到一次性切流。在 Nacos 2.0 中,也在有意的弱化集群的概念,畢竟開源還是要面向用戶的,有些東西適合阿里,但不一定適合開源,等再往后演進(jìn),集群這個(gè)概念又有可能重新回到大家的視線中了,history will repeat itself。

Group 分組隔離的概念可以參考 Dubbo 的服務(wù)隔離策略,其也有一個(gè)分組。支持分組的擴(kuò)展,用意當(dāng)然是好的,實(shí)際使用上,也的確有一些公司會(huì)習(xí)慣使用分組來進(jìn)行隔離。需要注意的一點(diǎn)是:Dubbo 注冊(cè)三元組(接口名+分組+版本)時(shí),其中 Dubbo 的分組是包含在 Nacos 的服務(wù)名中的,并不是映射成了 Nacos 的分組,一般 Nacos 注冊(cè)的服務(wù)是默認(rèn)注冊(cè)到 DEFAULT_GROUP 分組的。

Namespace 命名空間隔離,我認(rèn)為是 Nacos 一個(gè)比較好的設(shè)計(jì)。在實(shí)際場(chǎng)景中使用也比較普遍,一般用于多個(gè)環(huán)境的隔離,例如 daily,dev,test,uat,prod 等環(huán)境的隔離。特別是當(dāng)環(huán)境非常多時(shí),使用命名空間做邏輯隔離是一個(gè)比較節(jié)約成本的策略。但強(qiáng)烈建議大家僅僅在非線上環(huán)境使用 Namespace 進(jìn)行隔離,例如多套測(cè)試環(huán)境可以共享一套 Nacos,而線上環(huán)境單獨(dú)搭建另一套 Nacos 集群,以免線下測(cè)試流量干擾到線上環(huán)境。

服務(wù)發(fā)現(xiàn):推拉模型

上面介紹完了 Nacos 服務(wù)發(fā)現(xiàn)的 5 大領(lǐng)域模型,最后一節(jié),介紹下如何獲取服務(wù)模型。

Nacos 的服務(wù)發(fā)現(xiàn),有主動(dòng)拉取和推送兩種模式,這與一般的服務(wù)發(fā)現(xiàn)架構(gòu)相同。以下是拉模型的相關(guān)接口:

/** * Get all instances of a service * *@paramserviceName name of service *@returnA list of instance *@throwsNacosException */ListgetAllInstances(String serviceName)throwsNacosException;/** * Get qualified instances of service * *@paramserviceName name of service *@paramhealthy a flag to indicate returning healthy or unhealthy instances *@returnA qualified list of instance *@throwsNacosException */ListselectInstances(String serviceName,booleanhealthy)throwsNacosException;/** * Select one healthy instance of service using predefined load balance strategy * *@paramserviceName name of service *@returnqualified instance *@throwsNacosException */InstanceselectOneHealthyInstance(String serviceName)throwsNacosException;

Nacos 提供了三個(gè)同步拉取服務(wù)的方法,一個(gè)是查詢所有注冊(cè)的實(shí)例,一個(gè)是只查詢健康且上線的實(shí)例,還有一個(gè)是獲取一個(gè)健康且上線的實(shí)例。一般情況下,訂閱端并不關(guān)心不健康的實(shí)例或者權(quán)重設(shè)為 0 的實(shí)例,但是也不排除一些場(chǎng)景下,有一些運(yùn)維或者管理的場(chǎng)景需要拿到所有的實(shí)例。細(xì)心的讀者會(huì)注意到上述 Nacos 實(shí)例中有一個(gè) weight 字段,便是作用在此處的selectOneHealthyInstance接口上,按照權(quán)重返回一個(gè)健康的實(shí)例。個(gè)人認(rèn)為這個(gè)功能相對(duì)雞肋,一般的 RPC 框架都有自身配套的負(fù)載均衡策略,很少會(huì)由注冊(cè)中心 cover,事實(shí)上 Dubbo 和 Spring Cloud 都沒有用到 Nacos 的這個(gè)接口。

除了主動(dòng)查詢實(shí)例列表,Nacos還提供訂閱模式來感知服務(wù)下實(shí)例列表的變化,包括服務(wù)配置或者實(shí)例配置的變化。可以使用下面的接口來進(jìn)行訂閱或者取消訂閱:

/** * Subscribe service to receive events of instances alteration * *@paramserviceName name of service *@paramlistener event listener *@throwsNacosException */voidsubscribe(String serviceName, EventListener listener)throwsNacosException;/** * Unsubscribe event listener of service * *@paramserviceName name of service *@paramlistener event listener *@throwsNacosException */voidunsubscribe(String serviceName, EventListener listener)throwsNacosException;

在實(shí)際的服務(wù)發(fā)現(xiàn)中,訂閱接口尤為重要。消費(fèi)者啟動(dòng)時(shí),一般會(huì)同步獲取一次服務(wù)信息用于初始化,緊接著訂閱服務(wù),這樣當(dāng)服務(wù)發(fā)生上下線時(shí),就可以感知變化了,從而實(shí)現(xiàn)服務(wù)發(fā)現(xiàn)。

總結(jié)

Nacos 為了更好的實(shí)現(xiàn)服務(wù)發(fā)現(xiàn),提供一套成熟的服務(wù)模型,其中重點(diǎn)需要關(guān)注的是 Namespace、Service 和 Instance,得益于這一套服務(wù)模型的抽象,以及對(duì)推拉模型的支持,Nacos 可以快速被微服務(wù)框架集成。

理解了 Nacos 的服務(wù)模型,也有利于我們了解 Nacos 背后的工作原理,從而確保我們正確地使用 Nacos。但 Nacos 提供的這些模型也不一定所有都需要用上,例如集群、分組、權(quán)重等概念,被實(shí)踐證明是相對(duì)雞肋的設(shè)計(jì),在使用時(shí),也需要根據(jù)自身業(yè)務(wù)特點(diǎn)去評(píng)估特性用量,不要盲目地為了使用技術(shù)而去用。

掃碼了解更多中間件技術(shù)干貨和案例實(shí)踐:

轉(zhuǎn)載注明出處:華峰博客網(wǎng)

內(nèi)容版權(quán)聲明:除非注明,否則皆為本站原創(chuàng)文章。