來源:碼猿技術(shù)專欄
1. Nacos介紹
2. Nacos注冊中心實(shí)現(xiàn)原理分析
2.1 Nacos架構(gòu)圖
2.2 注冊中心的原理
3. Nacos源碼分析
3.1 Nacos服務(wù)注冊
3.2 Nacos服務(wù)發(fā)現(xiàn)
今天來分享一下Nacos注冊中心的底層原理,從服務(wù)注冊到服務(wù)發(fā)現(xiàn),非常細(xì)致
1. Nacos介紹
再講Nacos之前,先來講一下服務(wù)注冊和發(fā)現(xiàn)。我們知道,現(xiàn)在微服務(wù)架構(gòu)是目前開發(fā)的一個趨勢。服務(wù)消費(fèi)者要去調(diào)用多個服務(wù)提供者組成的集群。這里需要做到以下幾點(diǎn):
服務(wù)消費(fèi)者需要在本地配置文件中維護(hù)服務(wù)提供者集群的每個節(jié)點(diǎn)的請求地址。
服務(wù)提供者集群中如果某個節(jié)點(diǎn)宕機(jī),服務(wù)消費(fèi)者的本地配置中需要同步刪除這個節(jié)點(diǎn)的請求地址,防止請求發(fā)送到已經(jīng)宕機(jī)的節(jié)點(diǎn)上造成請求失敗。
因此需要引入服務(wù)注冊中心,它具有以下幾個功能:
服務(wù)地址的管理。
服務(wù)注冊。
服務(wù)動態(tài)感知。
而Nacos致力于解決微服務(wù)中的統(tǒng)一配置,服務(wù)注冊和發(fā)現(xiàn)等問題。Nacos集成了注冊中心和配置中心。其相關(guān)特性包括:
1.服務(wù)發(fā)現(xiàn)和服務(wù)健康監(jiān)測
Nacos支持基于DNS和RPC的服務(wù)發(fā)現(xiàn),即服務(wù)消費(fèi)者可以使用DNS或者HTTP的方式來查找和發(fā)現(xiàn)服務(wù)。Nacos提供對服務(wù)的實(shí)時的健康檢查,阻止向不健康的主機(jī)或者服務(wù)實(shí)例發(fā)送請求。Nacos支持傳輸層(Ping/TCP)、應(yīng)用層(HTTP、Mysql)的健康檢查。
2.動態(tài)配置服務(wù)
動態(tài)配置服務(wù)可以以中心化、外部化和動態(tài)化的方式管理所有環(huán)境的應(yīng)用配置和服務(wù)配置。
3.動態(tài)DNS服務(wù)
支持權(quán)重路由,讓開發(fā)者更容易的實(shí)現(xiàn)中間層的負(fù)載均衡、更靈活的路由策略、流量控制以及DNS解析服務(wù)。
4.服務(wù)和元數(shù)據(jù)管理
Nacos允許開發(fā)者從微服務(wù)平臺建設(shè)的視角來管理數(shù)據(jù)中心的所有服務(wù)和元數(shù)據(jù)。如:服務(wù)的生命周期、靜態(tài)依賴分析、服務(wù)的健康狀態(tài)、服務(wù)的流量管理、路由和安全策略等。
基于 Spring Boot + MyBatis Plus + Vue & Element 實(shí)現(xiàn)的后臺管理系統(tǒng) + 用戶小程序,支持 RBAC 動態(tài)權(quán)限、多租戶、數(shù)據(jù)權(quán)限、工作流、三方登錄、支付、短信、商城等功能
項(xiàng)目地址:https://gitee.com/zhijiantianya/ruoyi-vue-pro
視頻教程:https://doc.iocoder.cn/video/
2. Nacos注冊中心實(shí)現(xiàn)原理分析
2.1 Nacos架構(gòu)圖
以下是Nacos的架構(gòu)圖:
其中分為這么幾個模塊:
Provider APP:服務(wù)提供者。
Consumer APP:服務(wù)消費(fèi)者。
Name Server:通過Virtual IP或者DNS的方式實(shí)現(xiàn)Nacos高可用集群的服務(wù)路由。
Nacos Server:Nacos服務(wù)提供者。
OpenAPI:功能訪問入口。
Config Service、Naming Service:Nacos提供的配置服務(wù)、名字服務(wù)模塊。
Consistency Protocol:一致性協(xié)議,用來實(shí)現(xiàn)Nacos集群節(jié)點(diǎn)的數(shù)據(jù)同步,使用Raft算法實(shí)現(xiàn)。
其中包含:
Nacos Console :Nacos控制臺。
小總結(jié):
服務(wù)提供者通過VIP(Virtual IP)訪問Nacos Server高可用集群,基于OpenAPI完成服務(wù)的注冊和服務(wù)的查詢。
Nacos Server的底層則通過數(shù)據(jù)一致性算法(Raft)來完成節(jié)點(diǎn)的數(shù)據(jù)同步。
2.2 注冊中心的原理
這里對其原理做一個大致的介紹,在后文則從源碼角度進(jìn)行分析。
首先,服務(wù)注冊的功能體現(xiàn)在:
服務(wù)實(shí)例啟動時注冊到服務(wù)注冊表、關(guān)閉時則注銷(服務(wù)注冊)。
服務(wù)消費(fèi)者可以通過查詢服務(wù)注冊表來獲得可用的實(shí)例(服務(wù)發(fā)現(xiàn))。
服務(wù)注冊中心需要調(diào)用服務(wù)實(shí)例的健康檢查API來驗(yàn)證其是否可以正確的處理請求(健康檢查)。
Nacos服務(wù)注冊和發(fā)現(xiàn)的實(shí)現(xiàn)原理的圖如下:
基于 Spring Cloud Alibaba + Gateway + Nacos + RocketMQ + Vue & Element 實(shí)現(xiàn)的后臺管理系統(tǒng) + 用戶小程序,支持 RBAC 動態(tài)權(quán)限、多租戶、數(shù)據(jù)權(quán)限、工作流、三方登錄、支付、短信、商城等功能
項(xiàng)目地址:https://gitee.com/zhijiantianya/yudao-cloud
視頻教程:https://doc.iocoder.cn/video/
3. Nacos源碼分析
前提(在本地或者虛機(jī)上先啟動好Nacos) 這一部分從2個角度來講Nacos是如何實(shí)現(xiàn)的:
服務(wù)注冊。
服務(wù)發(fā)現(xiàn)
3.1 Nacos服務(wù)注冊
首先看下一個包:spring-cloud-commons
這個ServiceRegistry接口是SpringCloud提供的服務(wù)注冊的標(biāo)準(zhǔn),集成到SpringCloud中實(shí)現(xiàn)服務(wù)注冊的組件,都需要實(shí)現(xiàn)這個接口。來看下它的結(jié)構(gòu):
publicinterfaceServiceRegistry{ voidregister(Rregistration); voidderegister(Rregistration); voidclose(); voidsetStatus(Rregistration,Stringstatus); TgetStatus(Rregistration); }
那么對于Nacos而言,該接口的實(shí)現(xiàn)類是NacosServiceRegistry,該類在這個pom包下:
再回過頭來看spring-cloud-commons包:
spring.factories主要是包含了自動裝配的配置信息,如圖:
在我之前的文章里我有提到過,在spring.factories中配置EnableAutoConfiguration的內(nèi)容后,項(xiàng)目在啟動的時候,會導(dǎo)入相應(yīng)的自動配置類,那么也就允許對該類的相關(guān)屬性進(jìn)行一個自動裝配。那么顯然,在這里導(dǎo)入了AutoServiceRegistrationAutoConfiguration這個類,而這個類顧名思義是服務(wù)注冊相關(guān)的配置類。
該類的完整代碼如下:
@Configuration( proxyBeanMethods=false ) @Import({AutoServiceRegistrationConfiguration.class}) @ConditionalOnProperty( value={"spring.cloud.service-registry.auto-registration.enabled"}, matchIfMissing=true ) publicclassAutoServiceRegistrationAutoConfiguration{ @Autowired( required=false ) privateAutoServiceRegistrationautoServiceRegistration; @Autowired privateAutoServiceRegistrationPropertiesproperties; publicAutoServiceRegistrationAutoConfiguration(){ } @PostConstruct protectedvoidinit(){ if(this.autoServiceRegistration==null&&this.properties.isFailFast()){ thrownewIllegalStateException("AutoServiceRegistrationhasbeenrequested,butthereisnoAutoServiceRegistrationbean"); } } }
這里做一個分析,AutoServiceRegistrationAutoConfiguration中注入了AutoServiceRegistration實(shí)例,該類的關(guān)系圖如下:
我們先來看一下這個抽象類AbstractAutoServiceRegistration:
publicabstractclassAbstractAutoServiceRegistrationimplementsAutoServiceRegistration, ApplicationContextAware, ApplicationListener { publicvoidonApplicationEvent(WebServerInitializedEventevent){ this.bind(event); } }
這里實(shí)現(xiàn)了ApplicationListener接口,并且傳入了WebServerInitializedEvent作為泛型,啥意思嘞,意思是:
NacosAutoServiceRegistration監(jiān)聽WebServerInitializedEvent事件。
也就是WebServer初始化完成后,會調(diào)用對應(yīng)的事件綁定方法,調(diào)用onApplicationEvent(),該方法最終調(diào)用NacosServiceRegistry的register()方法(NacosServiceRegistry實(shí)現(xiàn)了Spring的一個服務(wù)注冊標(biāo)準(zhǔn)接口)。
對于register()方法,主要調(diào)用的是Nacos Client SDK中的NamingService下的registerInstance()方法完成服務(wù)的注冊。
publicvoidregister(Registrationregistration){ if(StringUtils.isEmpty(registration.getServiceId())){ log.warn("Noservicetoregisterfornacosclient..."); }else{ StringserviceId=registration.getServiceId(); Stringgroup=this.nacosDiscoveryProperties.getGroup(); Instanceinstance=this.getNacosInstanceFromRegistration(registration); try{ this.namingService.registerInstance(serviceId,group,instance); log.info("nacosregistry,{}{}{}:{}registerfinished",newObject[]{group,serviceId,instance.getIp(),instance.getPort()}); }catch(Exceptionvar6){ log.error("nacosregistry,{}registerfailed...{},",newObject[]{serviceId,registration.toString(),var6}); ReflectionUtils.rethrowRuntimeException(var6); } } } publicvoidregisterInstance(StringserviceName,StringgroupName,Instanceinstance)throwsNacosException{ if(instance.isEphemeral()){ BeatInfobeatInfo=newBeatInfo(); beatInfo.setServiceName(NamingUtils.getGroupedName(serviceName,groupName)); beatInfo.setIp(instance.getIp()); beatInfo.setPort(instance.getPort()); beatInfo.setCluster(instance.getClusterName()); beatInfo.setWeight(instance.getWeight()); beatInfo.setMetadata(instance.getMetadata()); beatInfo.setScheduled(false); longinstanceInterval=instance.getInstanceHeartBeatInterval(); beatInfo.setPeriod(instanceInterval==0L?DEFAULT_HEART_BEAT_INTERVAL:instanceInterval); //1.addBeatInfo()負(fù)責(zé)創(chuàng)建心跳信息實(shí)現(xiàn)健康監(jiān)測。因?yàn)镹acosServer必須要確保注冊的服務(wù)實(shí)例是健康的。 //而心跳監(jiān)測就是服務(wù)健康監(jiān)測的一種手段。 this.beatReactor.addBeatInfo(NamingUtils.getGroupedName(serviceName,groupName),beatInfo); } //2.registerService()實(shí)現(xiàn)服務(wù)的注冊 this.serverProxy.registerService(NamingUtils.getGroupedName(serviceName,groupName),groupName,instance); }
再來看一下心跳監(jiān)測的方法addBeatInfo():
publicvoidaddBeatInfo(StringserviceName,BeatInfobeatInfo){ LogUtils.NAMING_LOGGER.info("[BEAT]addingbeat:{}tobeatmap.",beatInfo); Stringkey=this.buildKey(serviceName,beatInfo.getIp(),beatInfo.getPort()); BeatInfoexistBeat=null; if((existBeat=(BeatInfo)this.dom2Beat.remove(key))!=null){ existBeat.setStopped(true); } this.dom2Beat.put(key,beatInfo); //通過schedule()方法,定時的向服務(wù)端發(fā)送一個數(shù)據(jù)包,然后啟動一個線程不斷地檢測服務(wù)端的回應(yīng)。 //如果在指定的時間內(nèi)沒有收到服務(wù)端的回應(yīng),那么認(rèn)為服務(wù)器出現(xiàn)了故障。 //參數(shù)1:可以說是這個實(shí)例的相關(guān)信息。 //參數(shù)2:一個long類型的時間,代表從現(xiàn)在開始推遲執(zhí)行的時間,默認(rèn)是5000 //參數(shù)3:時間的單位,默認(rèn)是毫秒,結(jié)合5000即代表每5秒發(fā)送一次心跳數(shù)據(jù)包 this.executorService.schedule(newBeatReactor.BeatTask(beatInfo),beatInfo.getPeriod(),TimeUnit.MILLISECONDS); MetricsMonitor.getDom2BeatSizeMonitor().set((double)this.dom2Beat.size()); }
心跳檢查如果正常,即代表這個需要注冊的服務(wù)是健康的,那么執(zhí)行下面的注冊方法registerInstance():
publicvoidregisterService(StringserviceName,StringgroupName,Instanceinstance)throwsNacosException{ LogUtils.NAMING_LOGGER.info("[REGISTER-SERVICE]{}registeringservice{}withinstance:{}",newObject[]{this.namespaceId,serviceName,instance}); Mapparams=newHashMap(9); params.put("namespaceId",this.namespaceId); params.put("serviceName",serviceName); params.put("groupName",groupName); params.put("clusterName",instance.getClusterName()); 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",JSON.toJSONString(instance.getMetadata())); //這里可以看出來,把上述服務(wù)實(shí)例的一些必要參數(shù)保存到一個Map中,通過OpenAPI的方式發(fā)送注冊請求 this.reqAPI(UtilAndComs.NACOS_URL_INSTANCE,params,(String)"POST"); }
下面直接Debug走一遍。兩個前提(這里不再展開):
啟動一個Nacos服務(wù)。
搞一個Maven項(xiàng)目,集成Nacos。
案例1:用Debug來理解Nacos服務(wù)注冊流程
1.項(xiàng)目初始化后,根據(jù)上文說法,會執(zhí)行抽象類AbstractAutoServiceRegistration下面的onApplicationEvent()方法,即事件被監(jiān)聽到。
2.作為抽象類的子類實(shí)現(xiàn)NacosAutoServiceRegistration,監(jiān)聽到Web服務(wù)啟動后, 開始執(zhí)行super.register()方法。
3.執(zhí)行NacosServiceRegistry下的register()方法(super),前面說過,集成到SpringCloud中實(shí)現(xiàn)服務(wù)注冊的組件,都需要實(shí)現(xiàn)ServiceRegistry這個接口,而對于Nacos而言,NacosServiceRegistry就是具體的實(shí)現(xiàn)子類。執(zhí)行注冊方法需要傳入的三個參數(shù):
實(shí)例名稱serviceId。
實(shí)例歸屬的組。
具體實(shí)例
而registerInstance()主要做兩件事:
檢查服務(wù)的健康(this.beatReactor.addBeatInfo())。
執(zhí)行服務(wù)的注冊(this.serverProxy.registerService())。
服務(wù)健康的檢查:檢查通過后,發(fā)送OpenAPI進(jìn)行服務(wù)的注冊:
服務(wù)注冊小總結(jié)☆:
這里來做一個大框架式的梳理(也許前面寫的有點(diǎn)亂,這里通過幾個問答的形式來進(jìn)行總結(jié))
問題1:Nacos的服務(wù)注冊為什么和spring-cloud-commons這個包扯上關(guān)系?
回答:
首先,Nacos的服務(wù)注冊肯定少不了pom包:spring-cloud-starter-alibaba-nacos-discovery吧。
這個包下面包括了spring-cloud-commons包,那么這個包有什么用?
spring-cloud-commons中有一個接口叫做ServiceRegistry,而集成到SpringCloud中實(shí)現(xiàn)服務(wù)注冊的組件,都需要實(shí)現(xiàn)這個接口。
因此對于需要注冊到Nacos上的服務(wù),也需要實(shí)現(xiàn)這個接口,那么具體的實(shí)現(xiàn)子類為NacosServiceRegistry。
問題2:為什么我的項(xiàng)目加了這幾個依賴,服務(wù)啟動時依舊沒有注冊到Nacos中?
回答:
本文提到過,進(jìn)行Nacos服務(wù)注冊的時候,會有一個事件的監(jiān)聽過程,而監(jiān)聽的對象是WebServer,因此,這個項(xiàng)目需要是一個Web項(xiàng)目!
因此查看你的pom文件中是否有依賴:spring-boot-starter-web。
問題3:除此之外,spring-cloud-commons這個包還有什么作用?
回答:
這個包下的spring.factories文件中,配置了相關(guān)的服務(wù)注冊的置類,即支持其自動裝配。
這個配置類叫做AutoServiceRegistrationAutoConfiguration。其注入了類AutoServiceRegistration,而NacosAutoServiceRegistration是該類的一個具體實(shí)現(xiàn)。
當(dāng)WebServer初始化的時候,通過綁定的事件監(jiān)聽器,會實(shí)現(xiàn)監(jiān)聽,執(zhí)行服務(wù)的注冊邏輯。
說白了:
第一件事情:引入一個Spring監(jiān)聽器,當(dāng)容器初始化后,執(zhí)行Nacos服務(wù)的注冊。
第二件事情:而Nacos服務(wù)注冊的方法的實(shí)現(xiàn),其需要實(shí)現(xiàn)的接口來自于該包下的ServiceRegistry接口。
接下來就對Nacos注冊的流程進(jìn)行一個總結(jié):
服務(wù)(項(xiàng)目)啟動時,根據(jù)spring-cloud-commons中spring.factories的配置,自動裝配了類AutoServiceRegistrationAutoConfiguration。
AutoServiceRegistrationAutoConfiguration類中注入了類AutoServiceRegistration,其最終實(shí)現(xiàn)子類實(shí)現(xiàn)了Spring的監(jiān)聽器。
根據(jù)監(jiān)聽器,執(zhí)行了服務(wù)注冊方法。而這個服務(wù)注冊方法則是調(diào)用了NacosServiceRegistry的register()方法。
該方法主要調(diào)用的是Nacos Client SDK中的NamingService下的registerInstance()方法完成服務(wù)的注冊。
registerInstance()方法主要做兩件事:服務(wù)實(shí)例的健康監(jiān)測和實(shí)例的注冊。
通過schedule()方法定時的發(fā)送數(shù)據(jù)包,檢測實(shí)例的健康。
若健康監(jiān)測通過,調(diào)用registerService()方法,通過OpenAPI方式執(zhí)行服務(wù)注冊,其中將實(shí)例Instance的相關(guān)信息存儲到HashMap中。
3.2 Nacos服務(wù)發(fā)現(xiàn)
有一點(diǎn)我們需要清楚:Nacos服務(wù)的發(fā)現(xiàn)發(fā)生在什么時候。例如:微服務(wù)發(fā)生遠(yuǎn)程接口調(diào)用的時候。一般我們在使用OpenFeign進(jìn)行遠(yuǎn)程接口調(diào)用時,都需要用到對應(yīng)的微服務(wù)名稱,而這個名稱就是用來進(jìn)行服務(wù)發(fā)現(xiàn)的。
舉個例子:
@FeignClient("test-application") publicinterfaceMyFeignService{ @RequestMapping("getInfoById") Rinfo(@PathVariable("id")Longid); }
接下來直接開始講重點(diǎn),Nacos在進(jìn)行服務(wù)發(fā)現(xiàn)的時候,會調(diào)用NacosServerList類下的getServers()方法:
publicclassNacosServerListextendsAbstractServerList{ privateList getServers(){ try{ Stringgroup=this.discoveryProperties.getGroup(); //1.通過唯一的serviceId(一般是服務(wù)名稱)和組來獲得對應(yīng)的所有實(shí)例。 List instances=this.discoveryProperties.namingServiceInstance().selectInstances(this.serviceId,group,true); //2.將List 轉(zhuǎn)換成List 數(shù)據(jù),然后返回。 returnthis.instancesToServerList(instances); }catch(Exceptionvar3){ thrownewIllegalStateException("Cannotgetserviceinstancesfromnacos,serviceId="+this.serviceId,var3); } } }
接下來來看一下NacosNamingService.selectInstances()方法:
publicListselectInstances(StringserviceName,StringgroupName,booleanhealthy)throwsNacosException{ returnthis.selectInstances(serviceName,groupName,healthy,true); }
該方法最終會調(diào)用到其重載方法:
publicListselectInstances(StringserviceName,StringgroupName,List clusters, booleanhealthy,booleansubscribe)throwsNacosException{ //保存服務(wù)實(shí)例信息的對象 ServiceInfoserviceInfo; //如果該消費(fèi)者訂閱了這個服務(wù),那么會在本地維護(hù)一個服務(wù)列表,服務(wù)從本地獲取 if(subscribe){ serviceInfo=this.hostReactor.getServiceInfo(NamingUtils.getGroupedName(serviceName,groupName),StringUtils.join(clusters,",")); }else{ //否則實(shí)例會從服務(wù)中心進(jìn)行獲取。 serviceInfo=this.hostReactor.getServiceInfoDirectlyFromServer(NamingUtils.getGroupedName(serviceName,groupName),StringUtils.join(clusters,",")); } returnthis.selectInstances(serviceInfo,healthy); }
這里應(yīng)該重點(diǎn)關(guān)注this.hostReactor這個對象,它里面比較重要的是幾個Map類型的存儲結(jié)構(gòu):
publicclassHostReactor{ privatestaticfinallongDEFAULT_DELAY=1000L; privatestaticfinallongUPDATE_HOLD_INTERVAL=5000L; //存放線程異步調(diào)用的一個回調(diào)結(jié)果 privatefinalMap>futureMap; //本地已存在的服務(wù)列表,key是服務(wù)名稱,value是ServiceInfo privateMap serviceInfoMap; //待更新的實(shí)例列表 privateMap updatingMap; //定時任務(wù)(負(fù)責(zé)服務(wù)列表的實(shí)時更新) privateScheduledExecutorServiceexecutor; .... }
再看一看它的getServiceInfo()方法:
publicServiceInfogetServiceInfo(StringserviceName,Stringclusters){ LogUtils.NAMING_LOGGER.debug("failover-mode:"+this.failoverReactor.isFailoverSwitch()); Stringkey=ServiceInfo.getKey(serviceName,clusters); if(this.failoverReactor.isFailoverSwitch()){ returnthis.failoverReactor.getService(key); }else{ //1.先通過serverName即服務(wù)名獲得一個serviceInfo ServiceInfoserviceObj=this.getServiceInfo0(serviceName,clusters); //如果沒有serviceInfo,則通過傳進(jìn)來的參數(shù)new出一個新的serviceInfo對象,并且同時維護(hù)到本地Map和更新Map //這里是serviceInfoMap和updatingMap if(null==serviceObj){ serviceObj=newServiceInfo(serviceName,clusters); this.serviceInfoMap.put(serviceObj.getKey(),serviceObj); this.updatingMap.put(serviceName,newObject()); //2.updateServiceNow(),立刻去Nacos服務(wù)端拉去數(shù)據(jù)。 this.updateServiceNow(serviceName,clusters); this.updatingMap.remove(serviceName); }elseif(this.updatingMap.containsKey(serviceName)){ synchronized(serviceObj){ try{ serviceObj.wait(5000L); }catch(InterruptedExceptionvar8){ LogUtils.NAMING_LOGGER.error("[getServiceInfo]serviceName:"+serviceName+",clusters:"+clusters,var8); } } } //3.定時更新實(shí)例信息 this.scheduleUpdateIfAbsent(serviceName,clusters); //最后返回服務(wù)實(shí)例數(shù)據(jù)(前面已經(jīng)進(jìn)行了更新) return(ServiceInfo)this.serviceInfoMap.get(serviceObj.getKey()); } }
來看下scheduleUpdateIfAbsent()方法:
//通過心跳的方式,每10秒去更新一次數(shù)據(jù),并不是只有在調(diào)用服務(wù)的時候才會進(jìn)行更新,而是通過定時任務(wù)來異步進(jìn)行。 publicvoidscheduleUpdateIfAbsent(StringserviceName,Stringclusters){ if(this.futureMap.get(ServiceInfo.getKey(serviceName,clusters))==null){ synchronized(this.futureMap){ if(this.futureMap.get(ServiceInfo.getKey(serviceName,clusters))==null){ //創(chuàng)建一個UpdateTask的更新線程任務(wù),每10秒去異步更新集合數(shù)據(jù) ScheduledFuture>future=this.addTask(newHostReactor.UpdateTask(serviceName,clusters)); this.futureMap.put(ServiceInfo.getKey(serviceName,clusters),future); } } } }
案例2:用Debug來理解Nacos服務(wù)發(fā)現(xiàn)流程
1.進(jìn)行遠(yuǎn)程接口調(diào)用,觸發(fā)服務(wù)的發(fā)現(xiàn),調(diào)用NacosServerList的getServers()方法。傳入的serviceId和對應(yīng)Feign接口上的接口@FeignClient中的名稱一致。
例如,我這里調(diào)用的Feign接口是:
@FeignClient("gulimall-member") publicinterfaceMemberFeignService{ @RequestMapping("/member/member/info/{id}") Rinfo(@PathVariable("id")Longid); }
這里可以看出來,返回的是一個Instance類型的List,對應(yīng)的服務(wù)也發(fā)現(xiàn)并返回了。
2.這里則調(diào)用了NacosNamingService的selectInstances()方法,我這里的subscribe值是true,即代表我這個消費(fèi)者直接訂閱了這個服務(wù),因此最終的信息是從本地Map中獲取,即Nacos維護(hù)了一個注冊列表。
3.再看下HostReactor的getServiceInfo()方法:最終所需要的結(jié)果是從serviceInfoMap中獲取,并且通過多個Map進(jìn)行維護(hù)服務(wù)實(shí)例,若存在數(shù)據(jù)的變化,還會通過強(qiáng)制睡眠5秒鐘的方式來等待數(shù)據(jù)的更新。
4.無論怎樣都會調(diào)用this.scheduleUpdateIfAbsent(serviceName, clusters)方法。
5.通過scheduleUpdateIfAbsent()方法定時的獲取實(shí)時的實(shí)例數(shù)據(jù),并且負(fù)責(zé)維護(hù)本地的服務(wù)注冊列表,若服務(wù)發(fā)生更新,則更新本地的服務(wù)數(shù)據(jù)。
服務(wù)發(fā)現(xiàn)小總結(jié)☆:
經(jīng)常有人說過,Nacos有個好處,就是當(dāng)一個服務(wù)掛了之后,短時間內(nèi)不會造成影響,因?yàn)橛袀€本地注冊列表,在服務(wù)不更新的情況下,服務(wù)還能夠正常的運(yùn)轉(zhuǎn),其原因如下:
Nacos的服務(wù)發(fā)現(xiàn),一般是通過訂閱的形式來獲取服務(wù)數(shù)據(jù)。
而通過訂閱的方式,則是從本地的服務(wù)注冊列表中獲?。梢岳斫鉃榫彺妫?。相反,如果不訂閱,那么服務(wù)的信息將會從Nacos服務(wù)端獲取,這時候就需要對應(yīng)的服務(wù)是健康的。(宕機(jī)就不能使用了)
在代碼設(shè)計(jì)上,通過Map來存放實(shí)例數(shù)據(jù),key為實(shí)例名稱,value為實(shí)例的相關(guān)信息數(shù)據(jù)(ServiceInfo對象)。
最后,服務(wù)發(fā)現(xiàn)的流程就是:
以調(diào)用遠(yuǎn)程接口(OpenFeign)為例,當(dāng)執(zhí)行遠(yuǎn)程調(diào)用時,需要經(jīng)過服務(wù)發(fā)現(xiàn)的過程。
服務(wù)發(fā)現(xiàn)先執(zhí)行NacosServerList類中的getServers()方法,根據(jù)遠(yuǎn)程調(diào)用接口上@FeignClient中的屬性作為serviceId傳入NacosNamingService.selectInstances()方法中進(jìn)行調(diào)用。
根據(jù)subscribe的值來決定服務(wù)是從本地注冊列表中獲取還是從Nacos服務(wù)端中獲取。
以本地注冊列表為例,通過調(diào)用HostReactor.getServiceInfo()來獲取服務(wù)的信息(serviceInfo),Nacos本地注冊列表由3個Map來共同維護(hù)。
本地Map–>serviceInfoMap,
更新Map–>updatingMap
異步更新結(jié)果Map–>futureMap,
最終的結(jié)果從serviceInfoMap當(dāng)中獲取。
HostReactor類中的getServiceInfo()方法通過this.scheduleUpdateIfAbsent() 方法和updateServiceNow()方法實(shí)現(xiàn)服務(wù)的定時更新和立刻更新。
而對于scheduleUpdateIfAbsent()方法,則通過線程池來進(jìn)行異步的更新,將回調(diào)的結(jié)果(Future)保存到futureMap中,并且發(fā)生提交線程任務(wù)時,還負(fù)責(zé)更新本地注冊列表中的數(shù)據(jù)。
-
HTTP
+關(guān)注
關(guān)注
0文章
504瀏覽量
31194 -
DNS
+關(guān)注
關(guān)注
0文章
218瀏覽量
19828 -
RPC
+關(guān)注
關(guān)注
0文章
111瀏覽量
11529 -
MySQL
+關(guān)注
關(guān)注
1文章
804瀏覽量
26528
原文標(biāo)題:Nacos 為什么這么強(qiáng)?
文章出處:【微信號:芋道源碼,微信公眾號:芋道源碼】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。
發(fā)布評論請先 登錄
相關(guān)推薦
評論