阿里巴巴 Dubbo Nacos 服務發現開發最佳實踐
Nacos 是阿里巴巴在服務發現與配置管理領域開源的新產品,由在其內部生產上久經考驗的相關產品如 Diamond,VIPServer 等演化而來。而其中的服務發現功能,在內部支撐著百萬級的連線和億級的 QPS 服務呼叫。開源的 Nacos 雖然為了能夠符合開源質量的標準,在內部基礎上做了一些程式碼的重構和精煉,但是整體架構和內部是一致的。阿里巴巴為什麼選用目前 Nacos 所使用的架構,這不是本篇文章要討論的,本文意在幫助大家找到一個使用 Nacos 服務發現的最佳姿勢,用法優劣並無絕對,也希望 Nacos 社群積極將怎麼使用 Nacos 服務發現功能開放交流及貢獻程式碼,大家一起參與 Nacos 產品的開源演進,為國內自主的服務發現產品添磚加瓦。
服務 - 叢集 - 例項
Nacos 服務發現提供與其他服務發現產品不太一樣的機制以及概念,在這裡稍作介紹,下文中的內容都會多次提到這裡介紹的概念,因此掌握這些概念,對於用好 Nacos 服務發現至關重要。
不同於 Consul, Eureka, Nacos 的服務發現使用的領域資料模型是服務 - 叢集 - 例項這樣的三層結構。最上面是服務,註冊端(服務釋出者)和訂閱端(服務消費者)使用服務來與其他服務做區分,服務發現中,服務是必須指定的。叢集則是中間一層,一個服務又會劃分為多個叢集,每個叢集都有它的自定義配置,Nacos 提供了一個預設叢集和相應的預設配置,在不需要多叢集的場景下,可以不用指定叢集。最下一層是例項,每個叢集又會包含多個例項,這樣對服務進行發現時,可以發現多個叢集的所有例項,也可以指定叢集,來發現特定叢集的例項。
環境準備
首先,需要有一個 Nacos Server 部署起來,目前 Nacos 支援單機模式,也支援叢集模式,部署文件可以參考 Nacos 快速入門。然後新增 Nacos 客戶端最新版本依賴:
Hello World
我們先來進行一個最簡單的服務註冊與發現。Nacos 支援從客戶端註冊服務例項和訂閱服務,具體步驟如下:
配置 Nacos 客戶端 Properties:
Properties properties = new Properties();
properties.setProperty(PropertyKeyConst.SERVER_ADDR, “127.0.0.1:8848”);
建立 Nacos Naming 客戶端:
NamingService namingService = NacosFactory.createNamingService(properties);
註冊一個例項:
namingService.registerInstance(“nacos.test.1”, InetAddress.getLocalHost().getHostAddress(), 8080);
查詢這個服務的例項:
System.out.println(namingService.getAllInstances(“nacos.test.1”));
至此一個最簡單的 Nacos 服務發現的使用已經完成了。這裡要對一些細節稍作解釋。首先在第一步中,屬性 PropertyKeyConst.SERVER_ADDR 表示的是 Nacos 服務端的地址,這個地址的格式為 IP:port,IP:port。對於單機版,只需要指定一個 IP:port。甚至您可以將埠省略,這樣將會訪問 Nacos 的預設埠 8848。在第二步中,將建立一個 NamingService 例項,客戶端將為該例項建立單獨的資源空間,包括快取、執行緒池以及配置等。Nacos 客戶端沒有對該例項做單例的限制,請小心維護這個例項,以防新建了多於預期的例項。第三步註冊服務中,使用的是最簡單的 API 註冊方式,只需要傳入服務名、IP、埠就可以。第四步是獲取服務下的所有例項列表,包括健康和不健康的。
構建自定義例項
在一些場景中,我們希望註冊的例項中,有一些能夠被分配更多的流量,而另外一些分配較少的流量,或者能夠傳入一些例項的元資訊儲存到 Nacos 服務端,例如 IP 所屬的應用或者所在的機房,這樣在客戶端可以根據服務下掛載的例項的元資訊,來自定義負載均衡模式。別擔心,我們有另外的註冊例項介面,讓你可以在註冊的時候指定例項的屬性:
/**
* Register a instance to service with specified instance properties
*
* @param serviceName name of service
* @param instance instance to register
* @throws NacosException
*/
void registerInstance(String serviceName, Instance instance) throws NacosException;
這個方法可以在註冊服務的時候,傳入一個 Instanc 例項,而在 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
*/
@JSONField(name = "valid")
private boolean healthy = true;
/**
* Cluster information of instance
*/
@JSONField(serialize = false)
private Cluster cluster = new Cluster();
/**
* Service information of instance
*/
@JSONField(serialize = false)
private Service service;
/**
* User extended attributes
*/
private Map<String, String> metadata = new HashMap<String, String>();
....
其中,InstanceId 是由服務端生成返回給客戶端,用於唯一標識該例項。IP、埠是例項的基本屬性,除此之外,還有 weight 權重,可以設定該例項所分配流量的多少,這應該也比較好理解,權重越大,例項分配的流量就會越大。healthy 欄位代表該例項是否健康,這個值也是由服務端返回給客戶端的。cluster 和 service 分別表示該例項對應的叢集和服務的一些資訊,這些資訊會在下面做介紹。最後是例項的後設資料,這個後設資料一個 String 對 String 的 Map。那麼可以用如下程式碼來註冊一個自定義例項:
Instance instance = new Instance();
instance.setIp(InetAddress.getLocalHost().getHostAddress());
instance.setPort(8080);
instance.setWeight(100);
Map<String, String> metadata = new HashMap<String, String>(16);
metadata.put(“app”, “nacos”);
metadata.put(“site”, “beijing”);
instance.setMetadata(metadata);
namingService.registerInstance(“nacos.test.1”, instance);
構建自定義叢集
Nacos 引入了叢集的概念,在服務這個維度下面,可以對服務下的例項列表再做個劃分。這在阿里巴巴內部非常普遍。一個典型的場景是這個服務下的例項,需要配置多種健康檢查方式,有一些例項使用 TCP 的健康檢查方式,另外一些使用 HTTP 的健康檢查方式。另一個場景是,這個服務下掛載的機器分屬不同的環境,我們希望能夠在某些情況下(包括演練)將某個環境的流量全部切走,這樣可以通過配置一個環境屬於一個叢集,來做到一次性切流。
在客戶端構建自定義叢集,有一些陷阱需要小心。當前我們只有註冊例項的介面,例項內部的 cluster 欄位可以配置叢集的屬性。但是多個例項之間如果配置了不同的叢集屬性,這時候會發生什麼呢?Nacos 只會接受第一次註冊該叢集所傳入的叢集屬性,也就是說,先註冊的例項,獲得優先權,將它對應的叢集資訊註冊到 Nacos 服務端。只有 Nacos 服務端已經存在該叢集的配置,後續的註冊請求裡的叢集資訊,都會被忽略。為了確保你的應用保持預期的行為,請務必讓同一個叢集下的例項使用相同的叢集配置。
下面來看看可以為叢集定義哪些配置:
public class Cluster {
/**
* Name of belonging service
*/
private String serviceName;
/**
* Name of cluster
*/
private String name = "";
/**
* Health check config of this cluster
*/
private AbstractHealthChecker healthChecker = new AbstractHealthChecker.Tcp();
/**
* Default registered port for instances in this cluster.
*/
private int defaultPort = 80;
/**
* Default health check port of instances in this cluster.
*/
private int defaultCheckPort = 80;
/**
* Whether or not use instance port to do health check.
*/
private boolean useIPPort4Check = true;
private Map<String, String> metadata = new HashMap<String, String>();
...
}
首先是叢集對應的服務名,用來表示該叢集所屬的服務;然後是叢集的名字、健康檢查方式、預設的埠、預設的健康檢查埠以及是否使用是的埠做健康檢查。我們先來說簡單的,預設埠表示註冊時例項預設的埠,這個在客戶端並沒有體現,但是當使用 API 註冊例項的時候,埠是可以不傳入的,此時就會用這個預設埠作為例項的埠。然後是預設的健康檢查埠,當健康檢查方式中沒有配置埠時,就會用這個埠來和例項通訊,進行健康檢查。下一個屬性是是否使用例項埠做健康檢查,如果設為 true,則會使用例項註冊的埠來和例項進行通訊。最後一個屬性是叢集的後設資料,Nacos 支援多個維度的後設資料,例項支援,叢集支援,下面介紹的服務屬性也支援。
健康檢查方式,客戶端心跳是一種模式,由客戶端主動上報健康狀態。服務端檢測是另外一種模式,Nacos 目前支援三種:TCP、HTTP 和 MYSQL。TCP 方式會從 Nacos 服務端建立一個連線到例項,如果連線建立成功,則表示該例項健康。HTTP 方式則會從 Nacos 服務端想例項發起一個 HTTP 請求,可以配置的屬性有訪問的相對路徑,訪問的 HTTP 頭部,這個頭部使用豎線進行分割,以及預期的請求返回碼,預設為 200:
private String path = “”;
private String headers = “”;
private int expectedResponseCode = 200;
MYSQL 健康檢查方式,則可以讓 Nacos 往例項執行一條 MySQL 命令,可以配置的屬性有使用者名稱、密碼和執行的命令。執行結果如果不拋異常,則表示例項健康:
private String user;
private String pwd;
private String cmd;
構建自定義服務
同理,服務也可能需要自定義的配置,Nacos 的服務隨著例項的註冊而存在,並隨著所有例項的登出而消亡。目前除了使用 HTTP API 可以修改服務的屬性外(這將在未來的篇章中進行介紹),就只能使用註冊例項時傳入服務屬性來進行服務的自定義配置。這裡的服務與 Consul 或者 Eureka 不同,Consul 與 Eureka 的服務其實就是指的例項,而每個例項有一個服務名,通過這個服務名來獲取相同服務名下的例項列表,服務本身並不是一個資料實體。在真正的生產環境中,我們認為服務本身也是具有資料儲存需求的,例如作用於服務下所有例項的配置、許可權控制等。雖然有一些配置可以放到例項級別,例如健康檢查是否開啟。但是當服務的規模成千上萬後,想要整體修改這些例項的健康檢查開關,就是一個繁重的運維操作。另一些配置,例如下文會提到的健康保護閾值,是一定是一個服務只有一個唯一的值的,多個值將會造成邏輯上的衝突。
/**
* 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>();
服務的屬性儲存在 Service 類中,自上而下,依次是服務的名稱、服務的健康保護閾值、服務的應用名、服務的分組、服務的健康檢查模式以及服務的後設資料。相關概念這裡不再做一一陳述,你可以參考 Nacos 官網 概念介紹。這裡要提到的是服務的健康保護閾值,在阿里巴巴內部,這個值被廣泛的設定和調優。在這裡對該屬性的初衷做一個簡單的介紹。分散式服務場景下的一個問題是在部分例項不健康的情況下,是否能夠將流所有流量引向其他健康例項?在一些情況下,這可能造成雪崩效應。即本來健康的例項被多餘的流量衝擊,也變得不健康,然後導致健康的例項越來越少,最後整個服務崩潰。此時可以使用這個健康保護閾值,當健康例項與所有例項的比例小於這個值的時候,則認為所有例項都是健康的,這樣雖然部分流量流向了不健康的例項,但是剩餘健康的例項還是能夠正常訪問的。
服務發現
Nacos 的服務發現,有主動拉取和推送兩種模式,這與一般的服務發現架構相同。在拉取方式中,提供了三個方法,一個是查詢所有註冊的例項,一個是隻查詢健康且上線的例項,還有一個是獲取一個健康且上線的例項。一般情況下,訂閱端並不關心不健康的例項或者權重設為 0 的例項,但是也不排除一些場景下,有一些運維或者管理的場景需要拿到所有的例項。目前的版本同時還支援根據服務端設定的負載均衡策略,來查詢單個可用的例項。就好像 DNS 解析一樣,雖然每次都返回一個後端 IP,但是整體可以保證域名掛載的所有 IP 會按照一定的策略都能夠被客戶端解析到。
/**
- Get all instances of a service
- @param serviceName name of service
- @return A list of instance
- @throws NacosException
*/
List 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 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;
前兩個查詢方法會返回所有例項的列表,這允許使用者通過額外的工作,將例項的權重或者後設資料運用到負載均衡中。對於一般的微服務場景,針對每個例項輪詢,這樣已經足夠了。事實上,不管是在 Eureka 還是 Consul 裡,其原生客戶端都是隻負責服務的發現,並不支援負載均衡。這樣就需要第三方的 ribbon 或者 fabio 來完成負載均衡工作,此時它們的負載均衡,是完全放在客戶端的。
Nacos 也會支援客戶端側的負載均衡,並支援使用者擴充套件的負載均衡策略。不過在阿里巴巴內部,通常只需要由服務端來配置負載均衡策略,所有的呼叫端不區分業務的使用同一套負載均衡策略。因為實際上,呼叫端往往並不關心自身訪問的服務的流量分配,而只需要一個可用的服務節點就可以了。而服務提供端,則由於其部署規模很大和部署環境的複雜,需要對環境資訊敏感的流量分配以及對流量的絕對控制權。這時,往往需要提供端審慎的配置好統一的負載均衡策略,來保證所有訂閱端按照這個策略來進行訪問。
除了主動查詢例項列表,Nacos 還提供訂閱模式來感知服務下例項列表的變化,包括服務配置或者例項配置的變化。可以使用下面的介面來進行訂閱或者取消訂閱:
/**
- 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;
控制檯使用
Nacos 0.3.0 版本上線了控制檯,作為生產環境基本的運維工具,服務發現也通過控制檯釋放了部分的運維能力。雖然控制檯承擔的是運維為主的工作,但是開發人員也需要通過控制檯來檢視當前服務的註冊狀態和健康狀態等,服務發現的控制檯頁面介紹可以參考 https://nacos.io/en-us/blog/discovery-console.html。雖然這篇文章中的一些頁面通過社群的反饋而做了細微的調整,但是通過這篇文章應該可以掌握怎麼使用服務發現的控制檯了。控制檯的啟動方式也很簡單,將 Nacos 安裝包下載安裝啟動(安裝教程)之後,直接訪問:http://localhost:8848/nacos/index.html 即可開啟最新的控制檯介面。
小 結
Nacos 目前的版本,整合了服務發現和配置管理的基本能力以及部分高階特性。作為最小生產可用版本,Nacos 未來還會繼續開放新特性,結合 SpringCloud、K8S、Dubbo 等生態,為開發者提供極致易用和穩定的服務管理和配置管理能力。在可預期的幾個版本內,將會支援後設資料的管理及 DNS 的服務發現。爭取將使用 Nacos,作為服務發現和配置管理選型的最佳實踐。
原文連結
https://mp.weixin.qq.com/s/demNavCoJNDdumKXKPEZQw
服務推薦
相關文章
- Dubbo+Nacos實現服務註冊和發現
- Go單體服務開發最佳實踐Go
- Go 單體服務開發最佳實踐Go
- 服務發現與配置管理高可用最佳實踐
- Nacos服務註冊與發現
- Getway實現nacos註冊及服務轉發
- Nacos服務註冊與發現原理
- SpringCloud 微服務最佳開發實踐SpringGCCloud微服務
- SpringCloudAlibaba - 整合 Nacos 實現服務註冊與發現SpringGCCloud
- Nacos 服務註冊與發現原理分析
- Nacos服務註冊與發現的原理
- 工商銀行基於 Dubbo 構建金融微服務架構的實踐-服務發現篇微服務架構
- Laravel 開發最佳實踐Laravel
- JavaScript 開發最佳實踐JavaScript
- REST開發最佳實踐REST
- nacos學習筆記之服務發現中心筆記
- 基於Docker部署Dubbo+Nacos服務Docker
- 初探Nacos(二)-- SpringCloud使用Nacos的服務註冊與發現SpringGCCloud
- Dubbo 如何成為連線異構微服務體系的最佳服務開發框架微服務框架
- 微服務5:服務註冊與發現(實踐篇)微服務
- 通過Nacos讓Nginx擁有服務發現能力Nginx
- 最佳實踐(1):安卓開發安卓
- 軟體開發最佳實踐
- 微服務實戰:服務發現的可行方案以及實踐案例微服務
- Docker實現服務發現Docker
- 微服務框架 Go-Micro 整合 Nacos 實戰之服務註冊與發現微服務框架Go
- Node 呼叫 dubbo 服務的探索及實踐
- Spring Cloud Alibaba | Nacos服務註冊與發現SpringCloud
- nacos配置&gateway配置服務發現一直報500Gateway
- Nacos配置管理最佳實踐
- 最佳實踐(2):iOS開發篇iOS
- CSS最佳實踐團隊開發CSS
- 物聯網開發最佳實踐
- etcd實現服務發現
- Spring Cloud Alibaba基礎教程:使用Nacos實現服務註冊與發現SpringCloud
- .net5+nacos+ocelot 配置中心和服務發現實現
- NodeJs服務註冊與服務發現實現NodeJS
- SpringBoot開發案例之整合Dubbo分散式服務Spring Boot分散式