開篇構想
在此之前,已經寫了十多篇Nacos的文章,感覺Nacos還值得更深入的學習一下。于是萌生了寫一個Nacos源碼系列專欄的文章。
寫作的目标呢,有兩個:第一,能夠系統的學習Nacos知識;第二,能夠基于Nacos學到涉及到的知識點或面;
展現形式呢,也有兩個:第一,單篇足夠簡單且又有價值;第二,發現代碼中的新穎之處;
源碼版本資訊
目前在生産實踐中建議大家采用1.4.2版本,但作為技術研究,本系列文章會基于2.0.2版本來僅僅講解。這是兩個跨度比較大的版本,建議大家配合源碼進行學習。
關于源碼拉取,環境搭建部分就不再贅述。下面就開始本篇文章,講解Nacos服務注冊的用戶端部分。
服務注冊資訊
講到服務注冊,我們先要了解一下Nacos都會将什麼資訊傳遞給伺服器。直接從Nacos Client項目的NamingTest看起:
Properties properties = new Properties();
properties.put(PropertyKeyConst.SERVER_ADDR, "127.0.0.1:8848");
properties.put(PropertyKeyConst.USERNAME, "nacos");
properties.put(PropertyKeyConst.PASSWORD, "nacos");
Instance instance = new Instance();
instance.setIp("1.1.1.1");
instance.setPort(800);
instance.setWeight(2);
Map<String, String> map = new HashMap<String, String>();
map.put("netType", "external");
map.put("version", "2.0");
instance.setMetadata(map);
NamingService namingService = NacosFactory.createNamingService(properties);
namingService.registerInstance("nacos.test.1", instance);
複制
這是服務注冊的核心所有代碼。僅從此處的代碼分析,可以看出,Nacos注冊服務執行個體時,包含了兩大類資訊:Nacos Server連接配接資訊和執行個體資訊。
Nacos Server連接配接資訊
Nacos Server連接配接資訊,存儲在Properties當中,包含以下資訊:
- Server位址:Nacos伺服器位址,屬性的key為serverAddr;
- 使用者名:連接配接Nacos服務的使用者名,屬性key為username,預設值為nacos;
- 密碼:連接配接Nacos服務的密碼,屬性key為password,預設值為nacos;
執行個體資訊
注冊執行個體資訊用Instance對象承載,注冊的執行個體資訊又分兩部分:執行個體基礎資訊和中繼資料。
執行個體基礎資訊包括:
- instanceId:執行個體的唯一ID;
- ip:執行個體IP,提供給消費者進行通信的位址;
- port: 端口,提供給消費者通路的端口;
- weight:權重,目前執行個體的權限,浮點類型(預設1.0D);
- healthy:健康狀況,預設true;
- enabled:執行個體是否準備好接收請求,預設true;
- ephemeral:執行個體是否為瞬時的,預設為true;
- clusterName:執行個體所屬的叢集名稱;
- serviceName:執行個體的服務資訊;
Instance類包含了執行個體的基礎資訊之外,還包含了用于存儲中繼資料的metadata(描述資料的資料),類型為HashMap。
從Demo中放了兩個資料:
- netType:顧名思義,網絡類型,這裡的值為external,也就是外網的意思;
- version:版本,Nacos的版本,這裡是2.0這個大版本。
除了Demo中這些“自定義”的資訊,在Instance類中還定義了一些預設資訊,這些資訊通過get方法提供:
public long getInstanceHeartBeatInterval() {
return getMetaDataByKeyWithDefault(PreservedMetadataKeys.HEART_BEAT_INTERVAL,
Constants.DEFAULT_HEART_BEAT_INTERVAL);
}
public long getInstanceHeartBeatTimeOut() {
return getMetaDataByKeyWithDefault(PreservedMetadataKeys.HEART_BEAT_TIMEOUT,
Constants.DEFAULT_HEART_BEAT_TIMEOUT);
}
public long getIpDeleteTimeout() {
return getMetaDataByKeyWithDefault(PreservedMetadataKeys.IP_DELETE_TIMEOUT,
Constants.DEFAULT_IP_DELETE_TIMEOUT);
}
public String getInstanceIdGenerator() {
return getMetaDataByKeyWithDefault(PreservedMetadataKeys.INSTANCE_ID_GENERATOR,
Constants.DEFAULT_INSTANCE_ID_GENERATOR);
}
複制
上面的get方法在需要中繼資料預設值時會被用到:
- preserved.heart.beat.interval:心跳間隙的key,預設為5s,也就是預設5秒進行一次心跳;
- preserved.heart.beat.timeout:心跳逾時的key,預設為15s,也就是預設15秒收不到心跳,執行個體将會标記為不健康;
- preserved.ip.delete.timeout:執行個體IP被删除的key,預設為30s,也就是30秒收不到心跳,執行個體将會被移除;
- preserved.instance.id.generator:執行個體ID生成器key,預設為simple;
這些都是Nacos預設提供的值,也就是目前執行個體注冊時會告訴Nacos Server說:我的心跳間隙、心跳逾時等對應的值是多少,你按照這個值來判斷我這個執行個體是否健康。當然,如果你想讓心跳“加速”,出現故障快速被移除,那可以跳短心跳間隙和逾時時間。但這也意味着給Nacos服務帶來一定的壓力。
有了這些資訊,我們基本是已經知道注冊執行個體時需要傳遞什麼參數,需要配置什麼參數了。
NamingService接口
NamingService接口是Nacos命名服務對外提供的一個統一接口,看對應的源碼就可以發現,它提供了大量執行個體相關的接口方法,比如:
- 服務執行個體注冊;
- 服務執行個體登出;
- 擷取服務執行個體清單;
- 擷取服務單個執行個體;
- 訂閱服務事件;
- 取消訂閱服務事件;
- 擷取所有(或指定)服務名稱;
- 擷取所有訂閱的服務;
- 擷取Nacos服務的狀态;
- 主動關閉服務;
其中部分功能提供了大量的重載方法,應用于不同場景和不同類型執行個體或服務的篩選。這個就不逐一說明,按照需要或注釋進行使用即可。
NamingService的執行個體化是通過NamingFactory類和上面提到的Nacos服務資訊:
public static NamingService createNamingService(Properties properties) throws NacosException {
try {
Class<?> driverImplClass = Class.forName("com.alibaba.nacos.client.naming.NacosNamingService");
Constructor constructor = driverImplClass.getConstructor(Properties.class);
return (NamingService) constructor.newInstance(properties);
} catch (Throwable e) {
throw new NacosException(NacosException.CLIENT_INVALID_PARAM, e);
}
}
複制
很明顯,這裡采用了反射的機制來執行個體化NamingService,接口的具體實作類為NacosNamingService類。
NacosNamingService的實作
在示例代碼中使用了NamingService#registerInstance方法來進行服務執行個體的注冊,該方法接收兩個參數,服務名稱和執行個體對象。
@Override
public void registerInstance(String serviceName, Instance instance) throws NacosException {
registerInstance(serviceName, Constants.DEFAULT_GROUP, instance);
}
複制
這個方法的最大作用是設定了目前執行個體的分組資訊。我們知道,在Nacos中,通過Namespace、group、Service、Cluster等一層層的将執行個體進行環境的隔離。在這裡設定了預設的分組為“DEFAULT_GROUP”。
緊接着調用的registerInstance方法如下:
@Override
public void registerInstance(String serviceName, String groupName, Instance instance) throws NacosException {
NamingUtils.checkInstanceIsLegal(instance);
clientProxy.registerService(serviceName, groupName, instance);
}
複制
這個方法實作了兩個功能:第一,檢查心跳時間設定的對不對,配置的逾時時間總不能比心跳間隔還短吧。第二,通過NamingClientProxy這個代理來執行服務注冊操作。
反觀NacosNamingService構造方法,會發現NamingClientProxy這個代理接口的具體實作是有NamingClientProxyDelegate來完成的。
NamingClientProxyDelegate中實作
NamingClientProxy調用registerService實際上調用的就是NamingClientProxyDelegate的對應方法:
@Override
public void registerService(String serviceName, String groupName, Instance instance) throws NacosException {
getExecuteClientProxy(instance).registerService(serviceName, groupName, instance);
}
複制
真正調用注冊服務的并不是代理實作類,而是根據目前執行個體是否為瞬時對象,來選擇對應的用戶端代理來進行請求的:
private NamingClientProxy getExecuteClientProxy(Instance instance) {
return instance.isEphemeral() ? grpcClientProxy : httpClientProxy;
}
複制
如果目前執行個體為瞬時對象,則采用gRPC協定(NamingGrpcClientProxy)進行請求,否則采用http協定(NamingHttpClientProxy)進行請求。預設為瞬時對象,也就是說,2.0版本中預設采用了gRPC協定進行與Nacos服務進行互動。
NamingGrpcClientProxy中實作
關于gRPC協定這部分我們會單獨進行講解,這裡暫時不做拓展。主要看其registerService方法實作:
@Override
public void registerService(String serviceName, String groupName, Instance instance) throws NacosException {
NAMING_LOGGER.info("[REGISTER-SERVICE] {} registering service {} with instance {}", namespaceId, serviceName,
instance);
namingGrpcConnectionEventListener.cacheInstanceForRedo(serviceName, groupName, instance);
InstanceRequest request = new InstanceRequest(namespaceId, serviceName, groupName,
NamingRemoteConstants.REGISTER_INSTANCE, instance);
requestToServer(request, Response.class);
}
複制
在NamingGrpcClientProxy中做了兩件事,一件事是通過事件監聽器緩存了目前注冊的執行個體資訊用于恢複。緩存的資料結構為ConcurrentMap<String, Instance>,key為“serviceName@@groupName”,value就是前面封裝的執行個體資訊。
另外一件事就是封裝了參數,基于gRPC協定進行服務的調用和結果的處理。
流程圖
下面來看一張流程圖,來彙總一下上面講到的整個業務邏輯:
小結
關于Nacos源碼分析的開篇就寫這麼多,主要分析了服務注冊需要哪些次元的資訊、用戶端提供的核心服務處理類(NamingService)以及注冊通信協定的選擇。其中的一些内容還可以細化,比如gRPC協定的實作等,我們後續文章繼續進行呈現。
部落客簡介:《SpringBoot技術内幕》技術圖書作者,酷愛鑽研技術,寫技術幹貨文章。