日本综合一区二区|亚洲中文天堂综合|日韩欧美自拍一区|男女精品天堂一区|欧美自拍第6页亚洲成人精品一区|亚洲黄色天堂一区二区成人|超碰91偷拍第一页|日韩av夜夜嗨中文字幕|久久蜜综合视频官网|精美人妻一区二区三区

RELATEED CONSULTING
相關咨詢
選擇下列產(chǎn)品馬上在線溝通
服務時間:8:30-17:00
你可能遇到了下面的問題
關閉右側工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營銷解決方案
Nacos源碼系列—關于服務注冊的那些事

簡介

首先我們在看Nacos源碼之前,要先想想為什么我們要讀源碼?是為了裝杯?還是為了在心儀的女神面前給她娓娓道來展示自己的代碼功底?當然不全是!

這都不是我們讀源碼的最終目的。作為一名技術人,上面的都是浮云,真正激勵我們的應該是能夠提升我們技術功底和整體技術大局觀。此乃大道也!閑言少敘,接下來我們就來看一看,看源碼究竟有什么好處。

  • 提升技術功底: 當我們去看源碼的時候,能夠學習源碼里面優(yōu)秀的設計思想,還含有設計模式和并發(fā)編程技術,解決問題的思路,能夠知其所以然。
  • 新技術學習能力: 當我們看多了源碼,對于一個新技術或者框架的掌握速度會有大幅度提升,能根據(jù)經(jīng)驗或官網(wǎng)資料快速掌握底層的實現(xiàn),技術更新迭代也可以更快的入手。
  • 快速解決問題能力: 遇到問題,尤其是框架源碼的問題,能夠更快速定位。
  • 面試獲取更高成功率: 現(xiàn)在出去面試,一般中高級一點的,都會問到框架源碼級別的實現(xiàn),如果能夠說出來,可以提升面試的成功率和薪資待遇,源碼面試是區(qū)別程序員水平另一面鏡子。
  • 認識更多圈子: 多活躍開源社區(qū),熟讀源碼后多思考,發(fā)現(xiàn)問題或需求主動參與開源技術研發(fā),與圈內大牛成為朋友。

閱讀源碼的方法

  • 搭建入門demo: 我們可以先看一下官網(wǎng)提供的文檔,搭建Demo,快速掌握框架的基本使用。
  • 看核心代碼: 對于初次看源碼的同學,不要太過于關注源碼的細枝末節(jié),先把主要核心流程梳理出來,找到其入口,分析靜態(tài)代碼,如果遇到問題,可以進行斷點調試。
  • 繪圖和筆記: 梳理好核心功能后,可以用流程圖記錄下來,好記性不如爛筆頭,同時對關鍵的源碼部分可以進行備注,分析參數(shù)的變化。同時要善于用Debug,來觀看源碼的執(zhí)行過程。
  • 復習總結: 當我們把框架的所有功能點的源碼都分析完成后,回到主流程在梳理一遍,最后在自己腦袋中形成一個閉環(huán),這樣源碼的核心內容和主流程就基本上理解了。

Nacos核心功能點

  • 服務注冊: Nacos Client會通過發(fā)送REST請求的方式向Nacos Server注冊自己的服務,提供自身的元數(shù)據(jù),比如IP地址,端口等信息。Nacos Server接收到注冊請求后,就會把這些元數(shù)據(jù)信息存儲在一個雙層的內存Map中。
  • 服務心跳: 在服務注冊后,Nacos Client會維護一個定時心跳來支持通知Nacos Server,說明服務一直處于可用狀態(tài),防止被剔除。默認5s發(fā)送一次心跳。
  • 服務健康檢查: Nacos Server會開啟一個定時任務用來檢查注冊服務實例的健康狀況,對于超過15s沒有收到客戶端心跳的實例會將它的healthy屬性設置為false(客戶端服務發(fā)現(xiàn)時不會發(fā)現(xiàn))。如果某個實例超過30秒沒有收到心跳,直接剔除該實例(被剔除的實例如果恢復發(fā)送心跳則會重新注冊)
  • 服務發(fā)現(xiàn): 服務消費者(Nacos Client)在調用服務提供者的服務時,會發(fā)送一個REST請求給Nacos Server,獲取上面注冊的服務清單,并且緩存在Nacos Client本地,同時在Nacos Client本地開啟一個定時任務定時拉取服務端最新的注冊表信息更新到本地緩存。
  • 服務同步: Nacos Server集群之間會互相同步服務實例,用來保證服務信息的一致性。

Nacos源碼下載

首先我們需要將Nacos的源碼下載下來,下載地址:https://github.com/alibaba/nacos。

我們將源碼下下來以后,導入到idea中。

proto編譯

當我們導入成功以后,會出現(xiàn)程序包com.alibaba.nacos.consistency.entity不存在的錯誤提示,這是因為Nacos底層的數(shù)據(jù)通信會基于protobuf對數(shù)據(jù)做序列化和反序列化,需要先將proto文件編譯為對應的Java代碼。

最簡單的 不安裝任何的東西 idea2021.2已經(jīng)捆綁安裝了這個。

可以通過mvn copmpile來在target自動生成他們。

Nacos缺少Istio依賴問題解決

我們只需要在文件根目錄下執(zhí)行以下命令即可:

mvn clean package -Dmaven.test.skip=true -Dcheckstyle.skip=true

做完以上兩步,我們就可以啟動Nacos的了。

啟動Nacos

首先我們找到 nacos-console 這個模塊,這個就是我們的管理后臺,找到它的啟動類,因為Nacos默認為集群啟動,所以我們要設置它為單機啟動,方便演示。

設置命令:

-Dnacos.standalone=true -Dnacos.home=E:\test\nacos。

啟動成功后,賬號密碼:nacos/nacos。

到這里我們Nacos的源碼啟動就完成了。

開啟源碼

我們先從客戶端服務的注冊開始說起,我們可以先想一想如果Nacos客戶端要注冊,會把什么信息傳遞給服務器?這里我們可以看到在 nacos-client下的NamingTest有這么一些信息。

@Ignore
public class NamingTest {
@Test
public void testServiceList() throws Exception {
//Nacos Server連接信息
Properties properties = new Properties();
//Nacos服務器地址
properties.put(PropertyKeyConst.SERVER_ADDR, "127.0.0.1:8848");
//連接Nacos服務的用戶名
properties.put(PropertyKeyConst.USERNAME, "nacos");
//連接Nacos服務的密碼
properties.put(PropertyKeyConst.PASSWORD, "nacos");
//實例信息
Instance instance = new Instance();
//實例IP,提供給消費者進行通信的地址
instance.setIp("1.1.1.1");
//端口,提供給消費者訪問的端口
instance.setPort(800);
//權重,當前實例的權限,浮點類型(默認1.0D)
instance.setWeight(2);
Map map = new HashMap();
map.put("netType", "external");
map.put("version", "2.0");
instance.setMetadata(map);
//關鍵代碼 創(chuàng)建自己的實例
NamingService namingService = NacosFactory.createNamingService(properties);
namingService.registerInstance("nacos.test.1", instance);
ThreadUtils.sleep(5000L);
List list = namingService.getAllInstances("nacos.test.1");
System.out.println(list);
ThreadUtils.sleep(30000L);
// ExpressionSelector expressionSelector = new ExpressionSelector();
// expressionSelector.setExpression("INSTANCE.metadata.registerSource = 'dubbo'");
// ListView serviceList = namingService.getServicesOfServer(1, 10, expressionSelector);

}
}

上面就是客戶端注冊的一個測試類,模仿了真實的服務注冊到Nacos的過程,包括NacosServer連接、實例的創(chuàng)建、實例屬性的賦值、注冊實例,所以在這個其中包含了服務注冊的核心代碼,從這里我們可以大致看出,它包含了兩個類的信息:Nacos Server連接信息和實例信息。

Nacos Server連接信息:

從上述中我們可以看到有關于Nacos Server連接信息是存儲在Properties中。

  • Server地址:Nacos服務器地址,屬性的key為serverAddr。
  • 用戶名:連接Nacos服務的用戶名,屬性key為username,默認值為nacos。
  • 密碼:連接Nacos服務的密碼,屬性key為password,默認值為nacos。

實例信息:

從上述測試中我們可以看到注冊實例信息用instance進行承載,而實例信息又分為兩部分,一個是基礎實例信息,一個是元數(shù)據(jù)信息。

實例基礎信息:

  • instanceId:實例的唯一ID。
  • ip:實例IP,提供給消費者進行通信的地址。
  • port:端口,提供給消費者訪問的端口。
  • weight:權重,當前實例的權限,浮點類型(默認1.0D)。
  • healthy:健康狀況,默認true。
  • enabled:實例是否準備好接收請求,默認true。
  • ephemeral:實例是否為瞬時的,默認為true。
  • clusterName:實例所屬的集群名稱。
  • serviceName:實例的服務信息。

元數(shù)據(jù):

元數(shù)據(jù)類型為HashMap,從當前Demo我們能夠看到的數(shù)據(jù)只有兩個。

  • netType:網(wǎng)絡類型,這里設置的值為external(外網(wǎng))。
  • version Nacos版本,這里為2.0。

除此之外,我們在Instance類中還可以看到一些默認信息,這些方法都是通過get方法進行提供的。

  //心跳間隙的key,默認為5s,也就是默認5秒進行一次心跳
public long getInstanceHeartBeatInterval() {
return getMetaDataByKeyWithDefault(PreservedMetadataKeys.HEART_BEAT_INTERVAL,
Constants.DEFAULT_HEART_BEAT_INTERVAL);
}
//心跳超時的key,默認為15s,也就是默認15秒收不到心跳,實例將會標記為不健康;
public long getInstanceHeartBeatTimeOut() {
return getMetaDataByKeyWithDefault(PreservedMetadataKeys.HEART_BEAT_TIMEOUT,
Constants.DEFAULT_HEART_BEAT_TIMEOUT);
}
//實例IP被刪除的key,默認為30s,也就是30秒收不到心跳,實例將會被移除;
public long getIpDeleteTimeout() {
return getMetaDataByKeyWithDefault(PreservedMetadataKeys.IP_DELETE_TIMEOUT,
Constants.DEFAULT_IP_DELETE_TIMEOUT);
}
//實例ID生成器key,默認為simple;
public String getInstanceIdGenerator() {
return getMetaDataByKeyWithDefault(PreservedMetadataKeys.INSTANCE_ID_GENERATOR,
Constants.DEFAULT_INSTANCE_ID_GENERATOR);
}

為什么要說這個呢?從這些參數(shù)中我們就可以了解到,我們服務的心跳間隙是多少以及超時時間,傳遞什么參數(shù)配置什么參數(shù),以此來了解我們的實例是否健康。同時我們也可以看到一個比較關鍵且核心的類,是真正創(chuàng)建實例的類 ——NamingService。

NamingService

NamingService是Nacos對外提供的一個統(tǒng)一的接口,當我們點進去查看,可以看到大概以下幾個方法,這些方法提供了不同的重載方法,方便我們用于不同的場景。

//服務實例注冊
void registerInstance(...) throws NacosException;
//服務實例注銷
void deregisterInstance(...) throws NacosException;
//獲取服務實例列表
List getAllInstances(...) throws NacosException;
//查詢健康服務實例
List selectInstances(...) throws NacosException;
//查詢集群中健康的服務實例
List selectInstances(....List clusters....)throws NacosException;
//使用負載均衡策略選擇一個健康的服務實例
Instance selectOneHealthyInstance(...) throws NacosException;
//訂閱服務事件
void subscribe(...) throws NacosException;
//取消訂閱服務事件
void unsubscribe(...) throws NacosException;
//獲取所有(或指定)服務名稱
ListView getServicesOfServer(...) throws NacosException;
//獲取所有訂閱的服務
List getSubscribeServices() throws NacosException;
//獲取Nacos服務的狀態(tài)
String getServerStatus();
//主動關閉服務
void shutDown() throws NacosException;

NamingService的實例化是通過NacosFactory.createNamingService(properties);實現(xiàn)的,內部源碼是通過反射來實現(xiàn)實例化過程。

 NamingService namingService = NacosFactory.createNamingService(properties);
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的具體實現(xiàn)。

//調用registerInstance方法
namingService.registerInstance("nacos.test.1", instance);
 @Override
public void registerInstance(String serviceName, Instance instance) throws NacosException {
//默認的分組為“DEFAULT_GROUP”
registerInstance(serviceName, Constants.DEFAULT_GROUP, instance);
}
 @Override
public void registerInstance(String serviceName, String groupName, Instance instance) throws NacosException {
//檢查心跳時間是否正常
NamingUtils.checkInstanceIsLegal(instance);
//通過代理注冊服務
clientProxy.registerService(serviceName, groupName, instance);
}

心跳檢測代碼

   //心跳間隙超過限制 返回錯誤
if (instance.getInstanceHeartBeatTimeOut() < instance.getInstanceHeartBeatInterval()
|| instance.getIpDeleteTimeout() < instance.getInstanceHeartBeatInterval()) {
throw new NacosException(NacosException.INVALID_PARAM,
"Instance 'heart beat interval' must less than 'heart beat timeout' and 'ip delete timeout'.");
}

通過代理注冊服務,我們了解到clientProxy代理接口是通過NamingClientProxyDelegate來完成,我們可以在init構造方法中得出,具體的實例對象。

  private void init(Properties properties) throws NacosException {
//使用NamingClientProxyDelegate來完成
this.clientProxy = new NamingClientProxyDelegate(this.namespace, serviceInfoHolder, properties, changeNotifier);
}

NamingClientProxyDelegate實現(xiàn)在NamingClientProxyDelegate中,真正調用注冊服務的并不是代理實現(xiàn)類,而且先判斷當前實例是否為瞬時對象后,來選擇對應的客戶端代理來進行請求。

 @Override
public void registerService(String serviceName, String groupName, Instance instance) throws NacosException {
getExecuteClientProxy(instance).registerService(serviceName, groupName, instance);
}

如果當前實例是瞬時對象,則采用gRPC協(xié)議(NamingGrpcClientProxy)進行請求,否則采用Http協(xié)議(NamingHttpClientProxy),默認為瞬時對象,在2.0版本中默認采用gRPC協(xié)議進行與Nacos服務進行交互。

    //判斷當前實例是否為瞬時對象
private NamingClientProxy getExecuteClientProxy(Instance instance) {
return instance.isEphemeral() ? grpcClientProxy : httpClientProxy;
}

NamingGrpcClientProxy中的實現(xiàn)

   @Override
public void registerService(String serviceName, String groupName, Instance instance) throws NacosException {
NAMING_LOGGER.info("[REGISTER-SERVICE] {} registering service {} with instance {}", namespaceId, serviceName,
instance);
//數(shù)據(jù)的緩存
redoService.cacheInstanceForRedo(serviceName, groupName, instance);
//gRPC進行服務調用
doRegisterService(serviceName, groupName, instance);
}

大體關系圖如下所示:

Nacos客戶端在項目的應用

我們想要讓某一個服務注冊到Nacos中,首先要引入一個依賴:

        
com.alibaba.cloud
spring-cloud-starter-alibaba-nacos-discovery

在依賴中,去查看SpringBoot自動裝配文件自動裝配文件META-INF/spring.factories。

通過SpringBoot的自動裝配來加載EnableAutoConfiguration對應的類,這里我們可以看到很多有關于Nacos相關的類,怎么知道哪個是我們真正需要關心的類,服務在注冊的時候走的是哪個,一般自動裝配,我們都會找到帶有“Auto”關鍵字的文件進行查看,然后再結合我們需要找的,我們是客戶端注冊服務,所以我們大體可以定位到NacosServiceRegistryAutoConfiguration這個文件。

查看NacosServiceRegistryAutoConfiguration源碼,在這里我們只需要關注最核心的nacosAutoServiceRegistration方法

而我們真正關心的只有三個類NacosAutoServiceRegistration類是注冊的核心,我們來看一下它的繼承關系。

 @Bean
@ConditionalOnBean(AutoServiceRegistrationProperties.class)
public NacosAutoServiceRegistration nacosAutoServiceRegistration(
NacosServiceRegistry registry,
AutoServiceRegistrationProperties autoServiceRegistrationProperties,
NacosRegistration registration) {
return new NacosAutoServiceRegistration(registry,
autoServiceRegistrationProperties, registration);
}

從上述內容中我們可以知道,Nacos服務自動注冊是從NacosServiceRegistryAutoConfiguration類開始的,并自動注冊到NacosAutoServiceRegistration類中。

在下圖中我們可以看到,主要是調用了super 方法,所以我們繼續(xù)查看該類的構造方法:AbstractAutoServiceRegistration。

public class NacosAutoServiceRegistration
extends AbstractAutoServiceRegistration {
public NacosAutoServiceRegistration(ServiceRegistry serviceRegistry,
AutoServiceRegistrationProperties autoServiceRegistrationProperties,
NacosRegistration registration) {
super(serviceRegistry, autoServiceRegistrationProperties);
this.registration = registration;
}
}

AbstractAutoServiceRegistration 實現(xiàn)了ApplicationListener接口,用來監(jiān)聽Spring容器啟動過程中WebServerInitializedEvent事件,一般如果我們實現(xiàn)這個類的時候,會實現(xiàn)一個方法onApplicationEvent(),這個方法會在我們項目啟動的時候觸發(fā)。

 @Override
@SuppressWarnings("deprecation")
public void onApplicationEvent(WebServerInitializedEvent event) {
bind(event);
}

由此我們可以看到bind里面的這個方法。

 @Deprecated
public void bind(WebServerInitializedEvent event) {
//獲取 ApplicationContext對象
ApplicationContext context = event.getApplicationContext();
//判斷服務的 Namespace
if (context instanceof ConfigurableWebServerApplicationContext) {
if ("management".equals(((ConfigurableWebServerApplicationContext) context).getServerNamespace())) {
return;
}
}
//記錄當前服務的端口
this.port.compareAndSet(0, event.getWebServer().getPort());
//【核心】啟動注冊流程
this.start();
}

start()方法調用register();方法來注冊服務。

 public void start() {
if (!isEnabled()) {
if (logger.isDebugEnabled()) {
logger.debug("Discovery Lifecycle disabled. Not starting");
}
return;
}
// only initialize if nonSecurePort is greater than 0 and it isn't already running
// because of containerPortInitializer below
//如果服務是沒有運行狀態(tài)時,進行初始化
if (!this.running.get()) {
//發(fā)布服務開始注冊事件
this.context.publishEvent(new InstancePreRegisteredEvent(this, getRegistration()));
//【核心】注冊服務
register();
if (shouldRegisterManagement()) {
registerManagement();
}
//發(fā)布注冊完成事件
this.context.publishEvent(new InstanceRegisteredEvent<>(this, getConfiguration()));
//服務狀態(tài)設置為運行狀態(tài)
this.running.compareAndSet(false, true);
}

}

NacosServiceRegistry.register()方法,如下所示:

@Override
public void register(Registration registration) {
//判斷ServiceId是否為空
if (StringUtils.isEmpty(registration.getServiceId())) {
log.warn("No service to register for nacos client...");
return;
}
//獲取Nacos的服務信息
NamingService namingService = namingService();
//獲取服務ID和分組
String serviceId = registration.getServiceId();
String group = nacosDiscoveryProperties.getGroup();
//構建instance實例(IP/Port/Weight/clusterName.....)
Instance instance = getNacosInstanceFromRegistration(registration);

try {
//向服務端注冊此服務
namingService.registerInstance(serviceId, group, instance);
log.info("nacos registry, {} {} {}:{} register finished", group, serviceId,
instance.getIp(), instance.getPort());
}
catch (Exception e) {
if (nacosDiscoveryProperties.isFailFast()) {
log.error("nacos registry, {} register failed...{},", serviceId,
registration.toString(), e);
rethrowRuntimeException(e);
}
else {
log.warn("Failfast is false. {} register failed...{},", serviceId,
registration.toString(), e);
}
}
}

NacosNamingService.registerInstance()方法,如下:

    @Override
public void registerInstance(String serviceName, String groupName, Instance instance) throws NacosException {
//檢查超時參數(shù)是否異常,心跳超時時間(15s)必須大于心跳間隙(5s)
NamingUtils.checkInstanceIsLegal(instance);
//拼接服務名,格式:groupName@@serviceName
String groupedServiceName = NamingUtils.getGroupedName(serviceName, groupName);
//判斷是否為臨時實例,默認為true
if (instance.isEphemeral()) {
//臨時實例,定時向Nacos服務發(fā)送心跳
BeatInfo beatInfo = beatReactor.buildBeatInfo(groupedServiceName, instance);
beatReactor.addBeatInfo(groupedServiceName, beatInfo);
}
//【核心】發(fā)送注冊服務實例請求
serverProxy.registerService(groupedServiceName, groupName, instance);
}

在registerService中我們可以看到Nacos服務注冊接口需要的完整參數(shù)。

    public void registerService(String serviceName, String groupName, Instance instance) throws NacosException {       
NAMING_LOGGER.info("[REGISTER-SERVICE] {} registering service {} with instance: {}", namespaceId, serviceName,
instance);
final Map params = new HashMap(16);
//環(huán)境
params.put(CommonParams.NAMESPACE_ID, namespaceId);
//服務名稱
params.put(CommonParams.SERVICE_NAME, serviceName);
//分組名稱
params.put(CommonParams.GROUP_NAME, groupName);
//集群名稱
params.put(CommonParams.CLUSTER_NAME, instance.getClusterName());
//當前實例IP
params.put("ip", instance.getIp());
//當前實例端口
params.put("port", String.valueOf(instance.getPort()));
//權重
params.put("weight", String.valueOf(instance.getWeight()));
params.put("enable", String.valueOf(instance.isEnabled()));
params.put("healthy", String.valueOf(instance.isHealthy()));
params.put("ephemeral", String.valueOf(instance.isEphemeral()));
params.put("metadata", JacksonUtils.toJson(instance.getMetadata()));
reqApi(UtilAndComs.nacosUrlInstance, params, HttpMethod.POST);
}

補充在這里我們會發(fā)現(xiàn)我們請求實例接口的地址為/nacos/v1/ns/instance,其實這個在官網(wǎng)中也有提供對應的地址給我們,并且是對應的。

客戶端注冊流程圖

總結

以上就是Nacos的客戶端注冊流程,閱讀源碼并沒有我們想象中的那么難,道阻且長,行之將至,當你開始行動的時候,你就已經(jīng)開始進步了,別管學多少。


標題名稱:Nacos源碼系列—關于服務注冊的那些事
鏈接地址:http://www.dlmjj.cn/article/cdcpojd.html