Nacos是阿里巴巴的微服務開源項目,用於服務發現和配置管理,開源以來我就一直關注,在此准備以幾篇文章來窺其全貌,但大段大段貼代碼就沒必要了,這里用自己的一些理解和總結來幫助大家理解。文章將基於截止目前最新發布的0.8版本,Nacos的使用方式參考官方文檔即可,這里主要從原理和實現上來講。
Nacos可以分為服務發現(Naming)和配置管理(Config)兩塊,而從使用上來說,又可分為Nacos服務端和客戶端,第一篇先來聊下服務發現(Naming)的客戶端。
Example
我們從官方示例入手。
Properties properties = new Properties(); properties.setProperty("serverAddr", System.getProperty("serverAddr")); properties.setProperty("namespace", System.getProperty("namespace")); NamingService naming = NamingFactory.createNamingService(properties); naming.registerInstance("nacos.test.3", "11.11.11.11", 8888, "TEST1"); naming.registerInstance("nacos.test.3", "2.2.2.2", 9999, "DEFAULT"); System.out.println(naming.getAllInstances("nacos.test.3")); naming.deregisterInstance("nacos.test.3", "2.2.2.2", 9999, "DEFAULT"); System.out.println(naming.getAllInstances("nacos.test.3")); naming.subscribe("nacos.test.3", new EventListener() { @Override public void onEvent(Event event) { System.out.println(((NamingEvent)event).getServiceName()); System.out.println(((NamingEvent)event).getInstances()); } });
NamingService
從官方示例可以了解到,對於我們使用者來說,NamingService是Nacos對外提供給使用者的接口,其實現類為com.alibaba.nacos.client.naming.NacosNamingService,歸納起來,NamingService提供了以下方法:
- registerInstance:注冊實例。
- deregisterInstance:注銷實例。
- getAllInstances:獲取某一服務的所有實例。
- selectInstances:獲取某一服務健康或不健康的實例。
- selectOneHealthyInstance:根據權重選擇一個健康的實例。
- getServerStatus:檢測服務端健康狀態。
- subscribe:注冊對某個服務的監聽。
- unsubscribe:注銷對某個服務的監聽。
- getSubscribeServices:獲取被監聽的服務。
- getServicesOfServer:獲取命名空間(namespace)下的所有服務名。【注:此方法有個小坑,參數pageNo要從1開始】
核心類
Naming Client的幾個核心類及其關系如下圖。我們分別來看一下這幾個類。
NacosNamingService
NacosNamingService是NamingService接口的實現類。實現了上面提到的那些方法。
此外,NacosNamingService還起到了初始化其他核心類的作用,因為對外提供的方法都是委托給其他核心類處理的。按順序將依次初始化EventDispatcher、NamingProxy、BeatReactor、HostReactor。
從NacosNamingService的構造函數我們也可以了解到,可以進行一些參數的自定義,總結如下(部分概念的含義可參考官方文檔):
EventDispatcher
EventDispatcher與其他事件分發的組件沒什么不同,用於處理subscribe、unsubscribe等等與服務監聽相關的方法,並分發NamingEvent到各Listener。
成員變量ConcurrentMap<String, List<EventListener>> observerMap保存了注冊的Listener,key為{服務名}@@{集群名},value為各個EventListener的列表。
EventDispatcher會啟動1個名為com.alibaba.nacos.naming.client.listener的線程用於處理事件的分發。
注意點:
- 分發NamingEvent時,按照subscribe(…)方法的調用順序串行依次調用EventListener的onEvent(…)方法。
- 調用subscribe(…)方法會引起對應Service的事件分發。
NamingProxy
NamingProxy用於與Nacos服務端通信,注冊服務、注銷服務、發送心跳等都經由NamingProxy來請求服務端。
NamingProxy會啟動1個名為com.alibaba.nacos.client.naming.serverlist.updater的線程,用於定期調用refreshSrvIfNeed()方法更新Nacos服務端地址,默認間隔為30秒,
對服務端API的調用將在后文總結。
注意點:refreshSrvIfNeed()方法對Nacos服務端地址的更新僅在使用endpoint的時候才會進行實際更新,如果是通過serverAddr配置的Nacos服務端地址,refreshSrvIfNeed()方法將不會進行任何操作。
BeatReactor
BeatReactor用於向Nacos服務端發送已注冊服務的心跳。
成員變量Map<String, BeatInfo> dom2Beat中保存了需要發送的BeatInfo,key為{serviceName}#{ip}#{port},value為對應的BeatInfo。
BeatReactor會啟動名為com.alibaba.nacos.naming.beat.sender的線程來發送心跳,默認線程數為1~CPU核心數的一半,可由namingClientBeatThreadCount參數指定。
默認情況下每5秒發送一次心跳,可根據Nacos服務端返回的clientBeatInterval的值調整心跳間隔。
注意點:0.8版本有一個小bug,客戶端心跳間隔並不受服務端返回值的控制。我已提交PR,預計將在0.9版本修復。
HostReactor
HostReactor用於獲取、保存、更新各Service實例信息。
成員變量Map<String, ServiceInfo> serviceInfoMap中保存了已獲取到的服務的信息,key為{服務名}@@{集群名}。
HostReactor會啟動名為com.alibaba.nacos.client.naming.updater的線程來更新服務信息,默認線程數為1~CPU核心數的一半,可由namingPollingThreadCount參數指定。定時任務UpdateTask會根據服務的cacheMillis值定時更新服務信息,默認值為10秒。該定時任務會在獲取某一服務信息時創建,保存在成員變量Map<String, ScheduledFuture<?>> futureMap中。
其他
PushReceiver
PushReceiver用於接收Nacos服務端的推送,初始化時會創建DatagramSocket使用UDP的方式接收推送。會啟動1個名為com.alibaba.nacos.naming.push.receiver的線程。
FailoverReactor
用於故障轉移,會啟動1個名為com.alibaba.nacos.naming.failover的線程並定時讀取名為00-00—000-VIPSRV_FAILOVER_SWITCH-000—00-00的文件,內容為1時表示開啟,此時獲取服務信息時會返回FailoverReactor緩存的服務信息。
Balancer
根據服務實例的權重挑選一個實例,實現簡單的負載均衡。
DiskCache
用於服務信息的持久化。
Naming API
API匯總如下:
Method | URI | 含義 |
---|---|---|
POST | /nacos/v1/ns/instance | 注冊實例 |
DELETE | /nacos/v1/ns/instance | 注銷實例 |
GET | /nacos/v1/ns/instance/list | 獲取實例列表 |
PUT | /nacos/v1/ns/instance/beat | 發送心跳 |
GET | /nacos/v1/ns/api/hello | Nacos服務端狀態 |
GET | /nacos/v1/ns/service/list | 獲取所有服務名 |
參數列表及示例
注冊實例
key | 含義 | 備注 |
---|---|---|
namespaceId | 命名空間 | 默認為public |
ip | 實例IP地址 | |
port | 實例端口 | |
weight | 權重 | 默認為1.0 |
enable | 是否開啟 | 默認為true |
healthy | 健康狀態 | 默認為true |
metadata | 其他信息 | |
serviceName | 服務名 | |
clusterName | 集群名 | 默認為DEFAULT |
返回示例:ok
注銷實例
key | 含義 | 備注 |
---|---|---|
namespaceId | 命名空間 | 默認為public |
ip | 實例IP地址 | |
port | 實例端口 | |
serviceName | 服務名 | |
clusterName | 集群名 | 默認為DEFAULT |
返回示例:ok
獲取實例列表
key | 含義 | 備注 |
---|---|---|
namespaceId | 命名空間 | 默認為public |
serviceName | 服務名 | |
clusters | 集群名 | 默認為DEFAULT |
udpPort | 監聽的UPD端口號 | 由PushReceiver創建 |
clientIP | 客戶端IP | |
healthyOnly | 是否只返回健康的實例 |
返回示例:{“metadata”:{},”dom”:”nacos.test.3”,”cacheMillis”:10000,”useSpecifiedURL”:false,”hosts”:[{“valid”:true,”marked”:false,”metadata”:{},”instanceId”:”2.2.2.2#9999#DEFAULT#nacos.test.3”,”port”:9999,”ip”:”2.2.2.2”,”clusterName”:”DEFAULT”,”weight”:1.0,”serviceName”:”nacos.test.3”,”enabled”:true},{“valid”:true,”marked”:false,”metadata”:{},”instanceId”:”11.11.11.11#8888#TEST1#nacos.test.3”,”port”:8888,”ip”:”11.11.11.11”,”clusterName”:”TEST1”,”weight”:1.0,”serviceName”:”nacos.test.3”,”enabled”:true}],”checksum”:”bd1054e6afb8d10730d945d74c4ce4421550584589236”,”lastRefTime”:1550584589236,”env”:””,”clusters”:””}
發送心跳
key | 含義 | 備注 |
---|---|---|
namespaceId | 命名空間 | 默認為public |
serviceName | 服務名 | |
beat | BeatInfo的JSON字符串 |
BeatInfo對象結構如下,與Instance對象類似:
field | 含義 | 備注 |
---|---|---|
port | 端口 | |
ip | IP地址 | |
weight | 權重 | |
metadata | 其他信息 | |
serviceName | 服務名 | |
clusterName | 集群名 | |
scheduled | 是否心跳中 | 這個是BeatReactor用來標識狀態的 |
返回示例:{“clientBeatInterval”:5000}
Nacos服務端狀態
key | 含義 | 備注 |
---|---|---|
namespaceId | 命名空間 | 默認為public |
請求示例:http://localhost:8848/nacos/v1/ns/api/hello?encoding=UTF-8&namespaceId=public&
返回示例:{“msg”:”Hello! I am Nacos-Naming and healthy! total services: raft 2, local port:8848”}
獲取所有服務名
key | 含義 | 備注 |
---|---|---|
namespaceId | 命名空間 | 默認為public |
pageNo | 頁碼 | 注意從1開始 |
pageSize | 返回數量 | |
selector | 過濾器 |
返回示例:{“count”:1,”doms”:[“nacos.test.3”]}
結語
Nacos服務發現的客戶端較為簡單,其他語言也可以參照其API來實現客戶端。如果對源碼實現感興趣,可以自己看下代碼。