阿里巴巴 Dubbo Nacos 服務發現開發最佳實踐

gt9000發表於2019-03-03

阿里雲幸運券
Nacos 是阿里巴巴在服務發現與配置管理領域開源的新產品,由在其內部生產上久經考驗的相關產品如 Diamond,VIPServer 等演化而來。而其中的服務發現功能,在內部支撐著百萬級的連線和億級的 QPS 服務呼叫。開源的 Nacos 雖然為了能夠符合開源質量的標準,在內部基礎上做了一些程式碼的重構和精煉,但是整體架構和內部是一致的。阿里巴巴為什麼選用目前 Nacos 所使用的架構,這不是本篇文章要討論的,本文意在幫助大家找到一個使用 Nacos 服務發現的最佳姿勢,用法優劣並無絕對,也希望 Nacos 社群積極將怎麼使用 Nacos 服務發現功能開放交流及貢獻程式碼,大家一起參與 Nacos 產品的開源演進,為國內自主的服務發現產品添磚加瓦。

服務 - 叢集 - 例項
Nacos 服務發現提供與其他服務發現產品不太一樣的機制以及概念,在這裡稍作介紹,下文中的內容都會多次提到這裡介紹的概念,因此掌握這些概念,對於用好 Nacos 服務發現至關重要。

不同於 Consul, Eureka, Nacos 的服務發現使用的領域資料模型是服務 - 叢集 - 例項這樣的三層結構。最上面是服務,註冊端(服務釋出者)和訂閱端(服務消費者)使用服務來與其他服務做區分,服務發現中,服務是必須指定的。叢集則是中間一層,一個服務又會劃分為多個叢集,每個叢集都有它的自定義配置,Nacos 提供了一個預設叢集和相應的預設配置,在不需要多叢集的場景下,可以不用指定叢集。最下一層是例項,每個叢集又會包含多個例項,這樣對服務進行發現時,可以發現多個叢集的所有例項,也可以指定叢集,來發現特定叢集的例項。

環境準備
首先,需要有一個 Nacos Server 部署起來,目前 Nacos 支援單機模式,也支援叢集模式,部署文件可以參考 Nacos 快速入門。然後新增 Nacos 客戶端最新版本依賴:

com.alibaba.nacos nacos-client [latest-version] 你可以配置從中央倉庫直接依賴,也可以將 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 還提供訂閱模式來感知服務下例項列表的變化,包括服務配置或者例項配置的變化。可以使用下面的介面來進行訂閱或者取消訂閱:

/**

小 結
Nacos 目前的版本,整合了服務發現和配置管理的基本能力以及部分高階特性。作為最小生產可用版本,Nacos 未來還會繼續開放新特性,結合 SpringCloud、K8S、Dubbo 等生態,為開發者提供極致易用和穩定的服務管理和配置管理能力。在可預期的幾個版本內,將會支援後設資料的管理及 DNS 的服務發現。爭取將使用 Nacos,作為服務發現和配置管理選型的最佳實踐。
騰訊雲代金券

原文連結

https://mp.weixin.qq.com/s/demNavCoJNDdumKXKPEZQw

服務推薦

相關文章