<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Nacos 為什么這么強(qiáng)?

          共 19611字,需瀏覽 40分鐘

           ·

          2022-10-02 10:04


          點(diǎn)擊關(guān)注公眾號(hào),Java干貨 及時(shí)送達(dá) ??

          95d84eeb224e78b2d8480bf2df93ad81.webp

          今天來分享一下Nacos注冊(cè)中心的底層原理,從服務(wù)注冊(cè)到服務(wù)發(fā)現(xiàn),非常細(xì)致

          1. Nacos介紹

          再講Nacos之前,先來講一下服務(wù)注冊(cè)和發(fā)現(xiàn)。我們知道,現(xiàn)在微服務(wù)架構(gòu)是目前開發(fā)的一個(gè)趨勢(shì)。服務(wù)消費(fèi)者要去調(diào)用多個(gè)服務(wù)提供者組成的集群。這里需要做到以下幾點(diǎn):

          1. 服務(wù)消費(fèi)者需要在本地配置文件中維護(hù)服務(wù)提供者集群的每個(gè)節(jié)點(diǎn)的請(qǐng)求地址。

          2. 服務(wù)提供者集群中如果某個(gè)節(jié)點(diǎn)宕機(jī),服務(wù)消費(fèi)者的本地配置中需要同步刪除這個(gè)節(jié)點(diǎn)的請(qǐng)求地址,防止請(qǐng)求發(fā)送到已經(jīng)宕機(jī)的節(jié)點(diǎn)上造成請(qǐng)求失敗。

          因此需要引入服務(wù)注冊(cè)中心,它具有以下幾個(gè)功能:

          1. 服務(wù)地址的管理。

          2. 服務(wù)注冊(cè)。

          3. 服務(wù)動(dòng)態(tài)感知。

          而Nacos致力于解決微服務(wù)中的統(tǒng)一配置,服務(wù)注冊(cè)和發(fā)現(xiàn)等問題。Nacos集成了注冊(cè)中心和配置中心。其相關(guān)特性包括:

          1.服務(wù)發(fā)現(xiàn)和服務(wù)健康監(jiān)測(cè)

          Nacos支持基于DNS和RPC的服務(wù)發(fā)現(xiàn),即服務(wù)消費(fèi)者可以使用DNS或者HTTP的方式來查找和發(fā)現(xiàn)服務(wù)。Nacos提供對(duì)服務(wù)的實(shí)時(shí)的健康檢查,阻止向不健康的主機(jī)或者服務(wù)實(shí)例發(fā)送請(qǐng)求。Nacos支持傳輸層(Ping/TCP)、應(yīng)用層(HTTP、Mysql)的健康檢查。

          2.動(dòng)態(tài)配置服務(wù)

          動(dòng)態(tài)配置服務(wù)可以以中心化、外部化和動(dòng)態(tài)化的方式管理所有環(huán)境的應(yīng)用配置和服務(wù)配置。

          3.動(dòng)態(tài)DNS服務(wù)

          支持權(quán)重路由,讓開發(fā)者更容易的實(shí)現(xiàn)中間層的負(fù)載均衡、更靈活的路由策略、流量控制以及DNS解析服務(wù)。

          4.服務(wù)和元數(shù)據(jù)管理

          Nacos允許開發(fā)者從微服務(wù)平臺(tái)建設(shè)的視角來管理數(shù)據(jù)中心的所有服務(wù)和元數(shù)據(jù)。如:服務(wù)的生命周期、靜態(tài)依賴分析、服務(wù)的健康狀態(tài)、服務(wù)的流量管理、路由和安全策略等。

          2. Nacos注冊(cè)中心實(shí)現(xiàn)原理分析

          2.1 Nacos架構(gòu)圖

          以下是Nacos的架構(gòu)圖:

          90c96b6322aeb1c741db97359155f869.webp圖片

          其中分為這么幾個(gè)模塊:

          • 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控制臺(tái)。

          小總結(jié):

          • 服務(wù)提供者通過VIP(Virtual IP)訪問Nacos Server高可用集群,基于OpenAPI完成服務(wù)的注冊(cè)和服務(wù)的查詢。

          • Nacos Server的底層則通過數(shù)據(jù)一致性算法(Raft)來完成節(jié)點(diǎn)的數(shù)據(jù)同步。

          2.2 注冊(cè)中心的原理

          這里對(duì)其原理做一個(gè)大致的介紹,在后文則從源碼角度進(jìn)行分析。

          首先,服務(wù)注冊(cè)的功能體現(xiàn)在:

          • 服務(wù)實(shí)例啟動(dòng)時(shí)注冊(cè)到服務(wù)注冊(cè)表、關(guān)閉時(shí)則注銷(服務(wù)注冊(cè))。

          • 服務(wù)消費(fèi)者可以通過查詢服務(wù)注冊(cè)表來獲得可用的實(shí)例(服務(wù)發(fā)現(xiàn))。

          • 服務(wù)注冊(cè)中心需要調(diào)用服務(wù)實(shí)例的健康檢查API來驗(yàn)證其是否可以正確的處理請(qǐng)求(健康檢查)。

          Nacos服務(wù)注冊(cè)和發(fā)現(xiàn)的實(shí)現(xiàn)原理的圖如下:

          5544ce6d529b83eae4d11590e1be9887.webp圖片

          3. Nacos源碼分析

          前提(在本地或者虛機(jī)上先啟動(dòng)好Nacos) 這一部分從2個(gè)角度來講Nacos是如何實(shí)現(xiàn)的:

          • 服務(wù)注冊(cè)。

          • 服務(wù)發(fā)現(xiàn)

          3.1 Nacos服務(wù)注冊(cè)

          首先看下一個(gè)包:spring-cloud-commons

          108ce877bc58a706f9c10c3e6498cf7f.webp圖片

          這個(gè)ServiceRegistry接口是SpringCloud提供的服務(wù)注冊(cè)的標(biāo)準(zhǔn),集成到SpringCloud中實(shí)現(xiàn)服務(wù)注冊(cè)的組件,都需要實(shí)現(xiàn)這個(gè)接口。來看下它的結(jié)構(gòu):

                
                public?interface?ServiceRegistry<R?extends?Registration>?{??
          ????void?register(R?registration);??
          ??
          ????void?deregister(R?registration);??
          ??
          ????void?close();??
          ??
          ????void?setStatus(R?registration,?String?status);??
          ??
          ????<T>?T?getStatus(R?registration);??
          }??

          那么對(duì)于Nacos而言,該接口的實(shí)現(xiàn)類是NacosServiceRegistry,該類在這個(gè)pom包下:

          1bb18264bb938cf84ea0b3bfa5c0dfe0.webp圖片

          再回過頭來看spring-cloud-commons包:

          7da5447b1c36c5c4807ecc8cf386cbe0.webp圖片

          spring.factories主要是包含了自動(dòng)裝配的配置信息,如圖:

          da9f3a76738ff616fb4cadf4ef3e8988.webp圖片

          在我之前的文章里我有提到過,在spring.factories中配置EnableAutoConfiguration的內(nèi)容后,項(xiàng)目在啟動(dòng)的時(shí)候,會(huì)導(dǎo)入相應(yīng)的自動(dòng)配置類,那么也就允許對(duì)該類的相關(guān)屬性進(jìn)行一個(gè)自動(dòng)裝配。那么顯然,在這里導(dǎo)入了AutoServiceRegistrationAutoConfiguration這個(gè)類,而這個(gè)類顧名思義是服務(wù)注冊(cè)相關(guān)的配置類。

          該類的完整代碼如下:

                
                @Configuration(??
          ????proxyBeanMethods?=?false??
          )??
          @Import({AutoServiceRegistrationConfiguration.class})??
          @ConditionalOnProperty(??
          ????value?
          =?{"spring.cloud.service-registry.auto-registration.enabled"},??
          ????matchIfMissing?=?true??
          )??
          public?class?AutoServiceRegistrationAutoConfiguration?{??
          ????@Autowired(??
          ????????required?=?false??
          ????)??
          ????private?AutoServiceRegistration?autoServiceRegistration;??
          ????@Autowired??
          ????private?AutoServiceRegistrationProperties?properties;??
          ??
          ????public?AutoServiceRegistrationAutoConfiguration()?{??
          ????}??
          ??
          ????@PostConstruct??
          ????protected?void?init()?{??
          ????????if?(this.autoServiceRegistration?==?null?&&?this.properties.isFailFast())?{??
          ????????????throw?new?IllegalStateException("Auto?Service?Registration?has?been?requested,?but?there?is?no?AutoServiceRegistration?bean");??
          ????????}??
          ????}??
          }??

          這里做一個(gè)分析,AutoServiceRegistrationAutoConfiguration中注入了AutoServiceRegistration實(shí)例,該類的關(guān)系圖如下:

          5e1467c152c61349ac6ea321d898b9fe.webp圖片

          我們先來看一下這個(gè)抽象類AbstractAutoServiceRegistration

                
                public?abstract?class?AbstractAutoServiceRegistration<R?extends?Registration>?implements?AutoServiceRegistration,???
          ApplicationContextAware,???
          ApplicationListener<WebServerInitializedEvent>?
          {??
          ?public?void?onApplicationEvent(WebServerInitializedEvent?event)?{??
          ?????this.bind(event);??
          ?}??
          }??

          這里實(shí)現(xiàn)了ApplicationListener接口,并且傳入了WebServerInitializedEvent作為泛型,啥意思嘞,意思是:

          • NacosAutoServiceRegistration監(jiān)聽WebServerInitializedEvent事件。

          • 也就是WebServer初始化完成后,會(huì)調(diào)用對(duì)應(yīng)的事件綁定方法,調(diào)用onApplicationEvent(),該方法最終調(diào)用NacosServiceRegistryregister()方法(NacosServiceRegistry實(shí)現(xiàn)了Spring的一個(gè)服務(wù)注冊(cè)標(biāo)準(zhǔn)接口)。

          對(duì)于register()方法,主要調(diào)用的是Nacos Client SDK中的NamingService下的registerInstance()方法完成服務(wù)的注冊(cè)。

                
                public?void?register(Registration?registration)?{??
          ????if?(StringUtils.isEmpty(registration.getServiceId()))?{??
          ????????log.warn("No?service?to?register?for?nacos?client...");??
          ????}?else?{??
          ????????String?serviceId?=?registration.getServiceId();??
          ????????String?group?=?this.nacosDiscoveryProperties.getGroup();??
          ????????Instance?instance?=?this.getNacosInstanceFromRegistration(registration);??
          ??
          ????????try?{??
          ????????????this.namingService.registerInstance(serviceId,?group,?instance);??
          ????????????log.info("nacos?registry,?{}?{}?{}:{}?register?finished",?new?Object[]{group,?serviceId,?instance.getIp(),?instance.getPort()});??
          ????????}?catch?(Exception?var6)?{??
          ????????????log.error("nacos?registry,?{}?register?failed...{},",?new?Object[]{serviceId,?registration.toString(),?var6});??
          ????????????ReflectionUtils.rethrowRuntimeException(var6);??
          ????????}??
          ??
          ????}??
          }??
          ??
          public?void?registerInstance(String?serviceName,?String?groupName,?Instance?instance)?throws?NacosException?{??
          ????if?(instance.isEphemeral())?{??
          ????????BeatInfo?beatInfo?=?new?BeatInfo();??
          ????????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);??
          ????????long?instanceInterval?=?instance.getInstanceHeartBeatInterval();??
          ????????beatInfo.setPeriod(instanceInterval?==?0L???DEFAULT_HEART_BEAT_INTERVAL?:?instanceInterval);??
          ????????//?1.addBeatInfo()負(fù)責(zé)創(chuàng)建心跳信息實(shí)現(xiàn)健康監(jiān)測(cè)。因?yàn)镹acos?Server必須要確保注冊(cè)的服務(wù)實(shí)例是健康的。??
          ????????//?而心跳監(jiān)測(cè)就是服務(wù)健康監(jiān)測(cè)的一種手段。??
          ????????this.beatReactor.addBeatInfo(NamingUtils.getGroupedName(serviceName,?groupName),?beatInfo);??
          ????}??
          ?//?2.registerService()實(shí)現(xiàn)服務(wù)的注冊(cè)??
          ????this.serverProxy.registerService(NamingUtils.getGroupedName(serviceName,?groupName),?groupName,?instance);??
          }??

          再來看一下心跳監(jiān)測(cè)的方法addBeatInfo()

                
                public?void?addBeatInfo(String?serviceName,?BeatInfo?beatInfo)?{??
          ????LogUtils.NAMING_LOGGER.info("[BEAT]?adding?beat:?{}?to?beat?map.",?beatInfo);??
          ????String?key?=?this.buildKey(serviceName,?beatInfo.getIp(),?beatInfo.getPort());??
          ????BeatInfo?existBeat?=?null;??
          ????if?((existBeat?=?(BeatInfo)this.dom2Beat.remove(key))?!=?null)?{??
          ????????existBeat.setStopped(true);??
          ????}??
          ??
          ????this.dom2Beat.put(key,?beatInfo);??
          ????//?通過schedule()方法,定時(shí)的向服務(wù)端發(fā)送一個(gè)數(shù)據(jù)包,然后啟動(dòng)一個(gè)線程不斷地檢測(cè)服務(wù)端的回應(yīng)。??
          ????//?如果在指定的時(shí)間內(nèi)沒有收到服務(wù)端的回應(yīng),那么認(rèn)為服務(wù)器出現(xiàn)了故障。??
          ????//?參數(shù)1:可以說是這個(gè)實(shí)例的相關(guān)信息。??
          ????//?參數(shù)2:一個(gè)long類型的時(shí)間,代表從現(xiàn)在開始推遲執(zhí)行的時(shí)間,默認(rèn)是5000??
          ????//?參數(shù)3:時(shí)間的單位,默認(rèn)是毫秒,結(jié)合5000即代表每5秒發(fā)送一次心跳數(shù)據(jù)包??
          ????this.executorService.schedule(new?BeatReactor.BeatTask(beatInfo),?beatInfo.getPeriod(),?TimeUnit.MILLISECONDS);??
          ????MetricsMonitor.getDom2BeatSizeMonitor().set((double)this.dom2Beat.size());??
          }??

          心跳檢查如果正常,即代表這個(gè)需要注冊(cè)的服務(wù)是健康的,那么執(zhí)行下面的注冊(cè)方法registerInstance()

                
                public?void?registerService(String?serviceName,?String?groupName,?Instance?instance)?throws?NacosException?{??
          ????LogUtils.NAMING_LOGGER.info("[REGISTER-SERVICE]?{}?registering?service?{}?with?instance:?{}",?new?Object[]{this.namespaceId,?serviceName,?instance});??
          ????Map<String,?String>?params?=?new?HashMap(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ù)保存到一個(gè)Map中,通過OpenAPI的方式發(fā)送注冊(cè)請(qǐng)求??
          ????this.reqAPI(UtilAndComs.NACOS_URL_INSTANCE,?params,?(String)"POST");??
          }??

          下面直接Debug走一遍。兩個(gè)前提(這里不再展開):

          • 啟動(dòng)一個(gè)Nacos服務(wù)。

          • 搞一個(gè)Maven項(xiàng)目,集成Nacos。

          案例1:用Debug來理解Nacos服務(wù)注冊(cè)流程

          1.項(xiàng)目初始化后,根據(jù)上文說法,會(huì)執(zhí)行抽象類AbstractAutoServiceRegistration下面的onApplicationEvent()方法,即事件被監(jiān)聽到。

          02a841fc14546b9c197617f4f883919e.webp圖片

          2.作為抽象類的子類實(shí)現(xiàn)NacosAutoServiceRegistration,監(jiān)聽到Web服務(wù)啟動(dòng)后, 開始執(zhí)行super.register()方法。

          675ee15765e7826756c6f1b678bc4635.webp圖片

          3.執(zhí)行NacosServiceRegistry下的register()方法(super),前面說過,集成到SpringCloud中實(shí)現(xiàn)服務(wù)注冊(cè)的組件,都需要實(shí)現(xiàn)ServiceRegistry這個(gè)接口,而對(duì)于Nacos而言,NacosServiceRegistry就是具體的實(shí)現(xiàn)子類。執(zhí)行注冊(cè)方法需要傳入的三個(gè)參數(shù):

          • 實(shí)例名稱serviceId。

          • 實(shí)例歸屬的組。

          • 具體實(shí)例

          13a656a8b193f6bf2e58d0e91f702531.webp圖片

          registerInstance()主要做兩件事:

          • 檢查服務(wù)的健康(this.beatReactor.addBeatInfo())。

          • 執(zhí)行服務(wù)的注冊(cè)(this.serverProxy.registerService())。

          服務(wù)健康的檢查:檢查通過后,發(fā)送OpenAPI進(jìn)行服務(wù)的注冊(cè):

          服務(wù)注冊(cè)小總結(jié)☆:

          這里來做一個(gè)大框架式的梳理(也許前面寫的有點(diǎn)亂,這里通過幾個(gè)問答的形式來進(jìn)行總結(jié))

          問題1:Nacos的服務(wù)注冊(cè)為什么和spring-cloud-commons這個(gè)包扯上關(guān)系?

          回答:

          1. 首先,Nacos的服務(wù)注冊(cè)肯定少不了pom包:spring-cloud-starter-alibaba-nacos-discovery吧。

          2. 這個(gè)包下面包括了spring-cloud-commons包,那么這個(gè)包有什么用?

          3. spring-cloud-commons中有一個(gè)接口叫做ServiceRegistry,而集成到SpringCloud中實(shí)現(xiàn)服務(wù)注冊(cè)的組件,都需要實(shí)現(xiàn)這個(gè)接口。

          4. 因此對(duì)于需要注冊(cè)到Nacos上的服務(wù),也需要實(shí)現(xiàn)這個(gè)接口,那么具體的實(shí)現(xiàn)子類為NacosServiceRegistry

          問題2:為什么我的項(xiàng)目加了這幾個(gè)依賴,服務(wù)啟動(dòng)時(shí)依舊沒有注冊(cè)到Nacos中?

          回答:

          1. 本文提到過,進(jìn)行Nacos服務(wù)注冊(cè)的時(shí)候,會(huì)有一個(gè)事件的監(jiān)聽過程,而監(jiān)聽的對(duì)象是WebServer,因此,這個(gè)項(xiàng)目需要是一個(gè)Web項(xiàng)目!

          2. 因此查看你的pom文件中是否有依賴:spring-boot-starter-web

          問題3:除此之外,spring-cloud-commons這個(gè)包還有什么作用?

          回答:

          1. 這個(gè)包下的spring.factories文件中,配置了相關(guān)的服務(wù)注冊(cè)的置類,即支持其自動(dòng)裝配。

          2. 這個(gè)配置類叫做AutoServiceRegistrationAutoConfiguration。其注入了類AutoServiceRegistration,而NacosAutoServiceRegistration是該類的一個(gè)具體實(shí)現(xiàn)。

          3. 當(dāng)WebServer初始化的時(shí)候,通過綁定的事件監(jiān)聽器,會(huì)實(shí)現(xiàn)監(jiān)聽,執(zhí)行服務(wù)的注冊(cè)邏輯。

          說白了:

          1. 第一件事情:引入一個(gè)Spring監(jiān)聽器,當(dāng)容器初始化后,執(zhí)行Nacos服務(wù)的注冊(cè)。

          2. 第二件事情:而Nacos服務(wù)注冊(cè)的方法的實(shí)現(xiàn),其需要實(shí)現(xiàn)的接口來自于該包下的ServiceRegistry接口。


          接下來就對(duì)Nacos注冊(cè)的流程進(jìn)行一個(gè)總結(jié):

          1. 服務(wù)(項(xiàng)目)啟動(dòng)時(shí),根據(jù)spring-cloud-commonsspring.factories的配置,自動(dòng)裝配了類AutoServiceRegistrationAutoConfiguration

          2. AutoServiceRegistrationAutoConfiguration類中注入了類AutoServiceRegistration,其最終實(shí)現(xiàn)子類實(shí)現(xiàn)了Spring的監(jiān)聽器。

          3. 根據(jù)監(jiān)聽器,執(zhí)行了服務(wù)注冊(cè)方法。而這個(gè)服務(wù)注冊(cè)方法則是調(diào)用了NacosServiceRegistryregister()方法。

          4. 該方法主要調(diào)用的是Nacos Client SDK中的NamingService下的registerInstance()方法完成服務(wù)的注冊(cè)。

          5. registerInstance()方法主要做兩件事:服務(wù)實(shí)例的健康監(jiān)測(cè)和實(shí)例的注冊(cè)。

          6. 通過schedule()方法定時(shí)的發(fā)送數(shù)據(jù)包,檢測(cè)實(shí)例的健康。

          7. 若健康監(jiān)測(cè)通過,調(diào)用registerService()方法,通過OpenAPI方式執(zhí)行服務(wù)注冊(cè),其中將實(shí)例Instance的相關(guān)信息存儲(chǔ)到HashMap中。

          3.2 Nacos服務(wù)發(fā)現(xiàn)

          有一點(diǎn)我們需要清楚:Nacos服務(wù)的發(fā)現(xiàn)發(fā)生在什么時(shí)候。例如:微服務(wù)發(fā)生遠(yuǎn)程接口調(diào)用的時(shí)候。一般我們?cè)谑褂肙penFeign進(jìn)行遠(yuǎn)程接口調(diào)用時(shí),都需要用到對(duì)應(yīng)的微服務(wù)名稱,而這個(gè)名稱就是用來進(jìn)行服務(wù)發(fā)現(xiàn)的。

          舉個(gè)例子:

                
                @FeignClient("test-application")??
          public?interface?MyFeignService?{??
          ????@RequestMapping("getInfoById")??
          ????R?info(@PathVariable("id")?Long?id);??
          }??

          接下來直接開始講重點(diǎn),Nacos在進(jìn)行服務(wù)發(fā)現(xiàn)的時(shí)候,會(huì)調(diào)用NacosServerList類下的getServers()方法:

                
                public?class?NacosServerList?extends?AbstractServerList<NacosServer>?{??
          ?private?List<NacosServer>?getServers()?{??
          ????????try?{??
          ????????????String?group?=?this.discoveryProperties.getGroup();??
          ????????????//?1.通過唯一的serviceId(一般是服務(wù)名稱)和組來獲得對(duì)應(yīng)的所有實(shí)例。??
          ????????????List<Instance>?instances?=?this.discoveryProperties.namingServiceInstance().selectInstances(this.serviceId,?group,?true);??
          ????????????//?2.將List<Instance>轉(zhuǎn)換成List<NacosServer>數(shù)據(jù),然后返回。??
          ????????????return?this.instancesToServerList(instances);??
          ????????}?catch?(Exception?var3)?{??
          ????????????throw?new?IllegalStateException("Can?not?get?service?instances?from?nacos,?serviceId="?+?this.serviceId,?var3);??
          ????????}??
          ????}??
          }??

          接下來來看一下NacosNamingService.selectInstances()方法:

                
                public?List<Instance>?selectInstances(String?serviceName,?String?groupName,?boolean?healthy)?throws?NacosException?{??
          ???return?this.selectInstances(serviceName,?groupName,?healthy,?true);??
          }??

          該方法最終會(huì)調(diào)用到其重載方法:

                
                public?List<Instance>?selectInstances(String?serviceName,?String?groupName,?List<String>?clusters,???
          ??boolean?healthy,?boolean?subscribe)
          ?throws?NacosException?
          {??
          ?//?保存服務(wù)實(shí)例信息的對(duì)象??
          ????ServiceInfo?serviceInfo;??
          ????//?如果該消費(fèi)者訂閱了這個(gè)服務(wù),那么會(huì)在本地維護(hù)一個(gè)服務(wù)列表,服務(wù)從本地獲取??
          ????if?(subscribe)?{??
          ????????serviceInfo?=?this.hostReactor.getServiceInfo(NamingUtils.getGroupedName(serviceName,?groupName),?StringUtils.join(clusters,?","));??
          ????}?else?{??
          ????//?否則實(shí)例會(huì)從服務(wù)中心進(jìn)行獲取。??
          ????????serviceInfo?=?this.hostReactor.getServiceInfoDirectlyFromServer(NamingUtils.getGroupedName(serviceName,?groupName),?StringUtils.join(clusters,?","));??
          ????}??
          ??
          ????return?this.selectInstances(serviceInfo,?healthy);??
          }??

          這里應(yīng)該重點(diǎn)關(guān)注this.hostReactor這個(gè)對(duì)象,它里面比較重要的是幾個(gè)Map類型的存儲(chǔ)結(jié)構(gòu):

                
                public?class?HostReactor?{??
          ????private?static?final?long?DEFAULT_DELAY?=?1000L;??
          ????private?static?final?long?UPDATE_HOLD_INTERVAL?=?5000L;??
          ????//?存放線程異步調(diào)用的一個(gè)回調(diào)結(jié)果??
          ????private?final?Map<String,?ScheduledFuture<?>>?futureMap;??
          ????//?本地已存在的服務(wù)列表,key是服務(wù)名稱,value是ServiceInfo??
          ????private?Map<String,?ServiceInfo>?serviceInfoMap;??
          ????//?待更新的實(shí)例列表??
          ????private?Map<String,?Object>?updatingMap;??
          ????//?定時(shí)任務(wù)(負(fù)責(zé)服務(wù)列表的實(shí)時(shí)更新)??
          ????private?ScheduledExecutorService?executor;??
          ????....??
          }??

          再看一看它的getServiceInfo()方法:

                
                public?ServiceInfo?getServiceInfo(String?serviceName,?String?clusters)?{??
          ????LogUtils.NAMING_LOGGER.debug("failover-mode:?"?+?this.failoverReactor.isFailoverSwitch());??
          ????String?key?=?ServiceInfo.getKey(serviceName,?clusters);??
          ????if?(this.failoverReactor.isFailoverSwitch())?{??
          ????????return?this.failoverReactor.getService(key);??
          ????}?else?{??
          ?????//?1.先通過serverName即服務(wù)名獲得一個(gè)serviceInfo??
          ????????ServiceInfo?serviceObj?=?this.getServiceInfo0(serviceName,?clusters);??
          ????????//?如果沒有serviceInfo,則通過傳進(jìn)來的參數(shù)new出一個(gè)新的serviceInfo對(duì)象,并且同時(shí)維護(hù)到本地Map和更新Map??
          ????????//?這里是serviceInfoMap和updatingMap??
          ????????if?(null?==?serviceObj)?{??
          ????????????serviceObj?=?new?ServiceInfo(serviceName,?clusters);??
          ????????????this.serviceInfoMap.put(serviceObj.getKey(),?serviceObj);??
          ????????????this.updatingMap.put(serviceName,?new?Object());??
          ????????????//?2.updateServiceNow(),立刻去Nacos服務(wù)端拉去數(shù)據(jù)。??
          ????????????this.updateServiceNow(serviceName,?clusters);??
          ????????????this.updatingMap.remove(serviceName);??
          ????????}?else?if?(this.updatingMap.containsKey(serviceName))?{??
          ????????????synchronized(serviceObj)?{??
          ????????????????try?{??
          ????????????????????serviceObj.wait(5000L);??
          ????????????????}?catch?(InterruptedException?var8)?{??
          ????????????????????LogUtils.NAMING_LOGGER.error("[getServiceInfo]?serviceName:"?+?serviceName?+?",?clusters:"?+?clusters,?var8);??
          ????????????????}??
          ????????????}??
          ????????}??
          ??//?3.定時(shí)更新實(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ù)的時(shí)候才會(huì)進(jìn)行更新,而是通過定時(shí)任務(wù)來異步進(jìn)行。??
          public?void?scheduleUpdateIfAbsent(String?serviceName,?String?clusters)?{??
          ????if?(this.futureMap.get(ServiceInfo.getKey(serviceName,?clusters))?==?null)?{??
          ????????synchronized(this.futureMap)?{??
          ????????????if?(this.futureMap.get(ServiceInfo.getKey(serviceName,?clusters))?==?null)?{??
          ?????????????//?創(chuàng)建一個(gè)UpdateTask的更新線程任務(wù),每10秒去異步更新集合數(shù)據(jù)??
          ????????????????ScheduledFuture<?>?future?=?this.addTask(new?HostReactor.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)用NacosServerListgetServers()方法。傳入的serviceId和對(duì)應(yīng)Feign接口上的接口@FeignClient中的名稱一致。

          a31b8b1017227b4bac2004183de4460b.webp圖片

          例如,我這里調(diào)用的Feign接口是:

                
                @FeignClient("gulimall-member")??
          public?interface?MemberFeignService?{??
          ????@RequestMapping("/member/member/info/{id}")??
          ????R?info(@PathVariable("id")?Long?id);??
          }??

          這里可以看出來,返回的是一個(gè)Instance類型的List,對(duì)應(yīng)的服務(wù)也發(fā)現(xiàn)并返回了。

          2.這里則調(diào)用了NacosNamingServiceselectInstances()方法,我這里的subscribe值是true,即代表我這個(gè)消費(fèi)者直接訂閱了這個(gè)服務(wù),因此最終的信息是從本地Map中獲取,即Nacos維護(hù)了一個(gè)注冊(cè)列表。

          3.再看下HostReactor的getServiceInfo()方法:最終所需要的結(jié)果是從serviceInfoMap中獲取,并且通過多個(gè)Map進(jìn)行維護(hù)服務(wù)實(shí)例,若存在數(shù)據(jù)的變化,還會(huì)通過強(qiáng)制睡眠5秒鐘的方式來等待數(shù)據(jù)的更新。

          4.無論怎樣都會(huì)調(diào)用this.scheduleUpdateIfAbsent(serviceName, clusters)方法。

          5.通過scheduleUpdateIfAbsent()方法定時(shí)的獲取實(shí)時(shí)的實(shí)例數(shù)據(jù),并且負(fù)責(zé)維護(hù)本地的服務(wù)注冊(cè)列表,若服務(wù)發(fā)生更新,則更新本地的服務(wù)數(shù)據(jù)。

          服務(wù)發(fā)現(xiàn)小總結(jié)☆:

          經(jīng)常有人說過,Nacos有個(gè)好處,就是當(dāng)一個(gè)服務(wù)掛了之后,短時(shí)間內(nèi)不會(huì)造成影響,因?yàn)橛袀€(gè)本地注冊(cè)列表,在服務(wù)不更新的情況下,服務(wù)還能夠正常的運(yùn)轉(zhuǎn),其原因如下:

          1. Nacos的服務(wù)發(fā)現(xiàn),一般是通過訂閱的形式來獲取服務(wù)數(shù)據(jù)。

          2. 而通過訂閱的方式,則是從本地的服務(wù)注冊(cè)列表中獲取(可以理解為緩存)。相反,如果不訂閱,那么服務(wù)的信息將會(huì)從Nacos服務(wù)端獲取,這時(shí)候就需要對(duì)應(yīng)的服務(wù)是健康的。(宕機(jī)就不能使用了)

          3. 在代碼設(shè)計(jì)上,通過Map來存放實(shí)例數(shù)據(jù),key為實(shí)例名稱,value為實(shí)例的相關(guān)信息數(shù)據(jù)(ServiceInfo對(duì)象)。

          最后,服務(wù)發(fā)現(xiàn)的流程就是:

          1. 以調(diào)用遠(yuǎn)程接口(OpenFeign)為例,當(dāng)執(zhí)行遠(yuǎn)程調(diào)用時(shí),需要經(jīng)過服務(wù)發(fā)現(xiàn)的過程。

          2. 服務(wù)發(fā)現(xiàn)先執(zhí)行NacosServerList類中的getServers()方法,根據(jù)遠(yuǎn)程調(diào)用接口上@FeignClient中的屬性作為serviceId傳入NacosNamingService.selectInstances()方法中進(jìn)行調(diào)用。

          3. 根據(jù)subscribe的值來決定服務(wù)是從本地注冊(cè)列表中獲取還是從Nacos服務(wù)端中獲取。

          4. 以本地注冊(cè)列表為例,通過調(diào)用HostReactor.getServiceInfo()來獲取服務(wù)的信息(serviceInfo),Nacos本地注冊(cè)列表由3個(gè)Map來共同維護(hù)。

          本地Map–>serviceInfoMap,

          更新Map–>updatingMap

          異步更新結(jié)果Map–>futureMap,

          最終的結(jié)果從serviceInfoMap當(dāng)中獲取。

          1. HostReactor類中的getServiceInfo()方法通過this.scheduleUpdateIfAbsent()?方法和updateServiceNow()方法實(shí)現(xiàn)服務(wù)的定時(shí)更新和立刻更新。

          2. 而對(duì)于scheduleUpdateIfAbsent()方法,則通過線程池來進(jìn)行異步的更新,將回調(diào)的結(jié)果(Future)保存到futureMap中,并且發(fā)生提交線程任務(wù)時(shí),還負(fù)責(zé)更新本地注冊(cè)列表中的數(shù)據(jù)。

          來源:碼猿技術(shù)專欄

              
                
                  
                    

          1.?JDK 19 / Java 19 正式發(fā)布,虛擬線程來了!

          2.?行存儲(chǔ) VS 列存儲(chǔ)

          3.?接了個(gè)變態(tài)需求:生成 Excel + PDF 導(dǎo)出,用 Java 怎么實(shí)現(xiàn)?

          4.?面試官: 美團(tuán)外賣的分庫分表怎么設(shè)計(jì)?

                      

          最近面試BAT,整理一份面試資料 Java面試BATJ通關(guān)手冊(cè) ,覆蓋了Java核心技術(shù)、JVM、Java并發(fā)、SSM、微服務(wù)、數(shù)據(jù)庫、數(shù)據(jù)結(jié)構(gòu)等等。

          獲取方式:點(diǎn)“ 在看 ”,關(guān)注公眾號(hào)并回復(fù)? Java ?領(lǐng)取,更多內(nèi)容陸續(xù)奉上。

                      

          PS:因公眾號(hào)平臺(tái)更改了推送規(guī)則,如果不想錯(cuò)過內(nèi)容,記得讀完點(diǎn)一下 在看 ,加個(gè) 星標(biāo) ,這樣每次新文章推送才會(huì)第一時(shí)間出現(xiàn)在你的訂閱列表里。

          點(diǎn)“在看”支持小哈呀,謝謝啦

          瀏覽 40
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  国产一级视频资源 | 日本一道码高清无码 | 亚洲精品午夜福利 | 91蜜桃精品 | 亚洲欧美精品AAAAAA片 |