<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>

          面試官:用過(guò)Nacos,那就說(shuō)說(shuō)Nacos服務(wù)注冊(cè)的原理吧!

          共 5333字,需瀏覽 11分鐘

           ·

          2021-11-05 14:18

          你知道的越多,不知道的就越多,業(yè)余的像一棵小草!

          你來(lái),我們一起精進(jìn)!你不來(lái),我和你的競(jìng)爭(zhēng)對(duì)手一起精進(jìn)!

          編輯:業(yè)余草

          cnblogs.com/wuzhenzhao/p/13625491.html

          推薦:https://www.xttblog.com/?p=5277

          Nacos 是目前國(guó)內(nèi)非?;鸬囊粋€(gè)服務(wù)注冊(cè)與發(fā)現(xiàn)的中間件,有不少公司都在采用 Nacos,因此面試中被問(wèn)到的概率也是非常高的!

          Nacos 服務(wù)注冊(cè)需要具備的能力:

          • 服務(wù)提供者把自己的協(xié)議地址注冊(cè)到Nacos server
          • 服務(wù)消費(fèi)者需要從Nacos Server上去查詢服務(wù)提供者的地址(根據(jù)服務(wù)名稱)
          • Nacos Server需要感知到服務(wù)提供者的上下線的變化
          • 服務(wù)消費(fèi)者需要?jiǎng)討B(tài)感知到Nacos Server端服務(wù)地址的變化

          作為注冊(cè)中心所需要的能力大多如此,我們需要做的是理解各種注冊(cè)中心的獨(dú)有特性,總結(jié)他們的共性。

          Nacos的實(shí)現(xiàn)原理:

          下面我們先來(lái)了解一下 Nacos 注冊(cè)中心的實(shí)現(xiàn)原理,通過(guò)下面這張圖來(lái)說(shuō)明。

          Nacos 注冊(cè)中心的實(shí)現(xiàn)原理

          圖中的流程是大家所熟悉的,不同的是在Nacos 中,服務(wù)注冊(cè)時(shí)在服務(wù)端本地會(huì)通過(guò)輪詢注冊(cè)中心集群節(jié)點(diǎn)地址進(jìn)行服務(wù)得注冊(cè),在注冊(cè)中心上,即Nacos Server上采用了Map保存實(shí)例信息,當(dāng)然配置了持久化的服務(wù)會(huì)被保存到數(shù)據(jù)庫(kù)中,在服務(wù)的調(diào)用方,為了保證本地服務(wù)實(shí)例列表的動(dòng)態(tài)感知,Nacos與其他注冊(cè)中心不同的是,采用了 Pull/Push同時(shí)運(yùn)作的方式。通過(guò)這些我們對(duì)Nacos注冊(cè)中心的原理有了一定的了解。我們從源碼層面去驗(yàn)證這些理論知識(shí)。

          Nacos的源碼分析(結(jié)合spring-cloud-alibaba +dubbo +nacos 的整合):

          「服務(wù)注冊(cè)的流程:」

          在基于Dubbo服務(wù)發(fā)布的過(guò)程中, 自動(dòng)裝配是走的事件監(jiān)聽(tīng)機(jī)制,在 DubboServiceRegistrationNonWebApplicationAutoConfiguration 這個(gè)類中,這個(gè)類會(huì)監(jiān)聽(tīng) ApplicationStartedEvent 事件,這個(gè)事件是spring boot在2.0新增的,就是當(dāng)spring boot應(yīng)用啟動(dòng)完成之后會(huì)發(fā)布這個(gè)時(shí)間。而此時(shí)監(jiān)聽(tīng)到這個(gè)事件之后,會(huì)觸發(fā)注冊(cè)的動(dòng)作。

          @EventListener(ApplicationStartedEvent.class)
          public?void?onApplicationStarted()?
          {
          ????setServerPort();
          ????register();
          }

          private?void?register()?{
          ????if?(registered)?{
          ????????return;
          ????}
          ????serviceRegistry.register(registration);
          ????registered?=?true;
          }

          serviceRegistry是 spring-cloud 提供的接口實(shí)現(xiàn)(org.springframework.cloud.client.serviceregistry.ServiceRegistry),很顯然注入的實(shí)例是:NacosServiceRegistry。

          NacosServiceRegistry

          然后進(jìn)入到實(shí)現(xiàn)類的注冊(cè)方法:

          @Override
          ????public?void?register(Registration?registration)?{

          ????????if?(StringUtils.isEmpty(registration.getServiceId()))?{
          ????????????log.warn("No?service?to?register?for?nacos?client...");
          ????????????return;
          ????????}
          ????????//對(duì)應(yīng)當(dāng)前應(yīng)用的application.name
          ????????String?serviceId?=?registration.getServiceId();
          ????????//表示nacos上的分組配置
          ????????String?group?=?nacosDiscoveryProperties.getGroup();
          ????????//表示服務(wù)實(shí)例信息
          ????????Instance?instance?=?getNacosInstanceFromRegistration(registration);

          ????????try?{
          ????????????//通過(guò)命名服務(wù)進(jìn)行注冊(cè)
          ????????????namingService.registerInstance(serviceId,?group,?instance);
          ????????????log.info("nacos?registry,?{}?{}?{}:{}?register?finished",?group,?serviceId,
          ????????????????????instance.getIp(),?instance.getPort());
          ????????}
          ????????catch?(Exception?e)?{
          ????????????log.error("nacos?registry,?{}?register?failed...{},",?serviceId,
          ????????????????????registration.toString(),?e);
          ????????????//?rethrow?a?RuntimeException?if?the?registration?is?failed.
          ????????????//?issue?:?https://github.com/alibaba/spring-cloud-alibaba/issues/1132
          ????????????rethrowRuntimeException(e);
          ????????}
          ????}????

          接下去就是開(kāi)始注冊(cè)實(shí)例,主要做兩個(gè)動(dòng)作

          1. 如果當(dāng)前注冊(cè)的是臨時(shí)節(jié)點(diǎn),則構(gòu)建心跳信息,通過(guò)beat反應(yīng)堆來(lái)構(gòu)建心跳任務(wù)
          2. 調(diào)用registerService發(fā)起服務(wù)注冊(cè)
          @Override
          public?void?registerInstance(String?serviceName,?String?groupName,?Instance?instance)?throws?NacosException?{
          ????????////是否是臨時(shí)節(jié)點(diǎn),如果是臨時(shí)節(jié)點(diǎn),則構(gòu)建心跳信息
          ????????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);

          ????????????//beatReactor,?添加心跳信息進(jìn)行處理
          ????????beatReactor.addBeatInfo(NamingUtils.getGroupedName(serviceName,?groupName),?beatInfo);
          ????????}
          ??????????//調(diào)用服務(wù)代理類進(jìn)行注冊(cè)???
          ??????????serverProxy.registerService(NamingUtils.getGroupedName(serviceName,?groupName),?groupName,?instance);
          }

          然后調(diào)用 NamingProxy ?的注冊(cè)方法進(jìn)行注冊(cè),代碼邏輯很簡(jiǎn)單,構(gòu)建請(qǐng)求參數(shù),發(fā)起請(qǐng)求。

          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(8);
          ????????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());
          ????????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()));

          ????????reqAPI(UtilAndComs.NACOS_URL_INSTANCE,?params,?HttpMethod.POST);

          ????}

          往下走我們就會(huì)發(fā)現(xiàn)上面提到的,服務(wù)在進(jìn)行注冊(cè)的時(shí)候會(huì)輪詢配置好的注冊(cè)中心的地址:

          public?String?reqAPI(String?api,?Map?params,?List?servers,?String?method)?{

          ????????params.put(CommonParams.NAMESPACE_ID,?getNamespaceId());

          ????????if?(CollectionUtils.isEmpty(servers)?&&?StringUtils.isEmpty(nacosDomain))?{
          ????????????throw?new?IllegalArgumentException("no?server?available");
          ????????}

          ????????Exception?exception?=?new?Exception();
          ????????//如果服務(wù)地址不為空
          ????????if?(servers?!=?null?&&?!servers.isEmpty())?{
          ????????????//隨機(jī)獲取一臺(tái)服務(wù)器節(jié)點(diǎn)
          ????????????Random?random?=?new?Random(System.currentTimeMillis());
          ????????????int?index?=?random.nextInt(servers.size());
          ????????????//?遍歷服務(wù)列表
          ????????????for?(int?i?=?0;?i?????????????????String?server?=?servers.get(index);//獲得索引位置的服務(wù)節(jié)點(diǎn)
          ????????????????try?{//調(diào)用指定服務(wù)
          ????????????????????return?callServer(api,?params,?server,?method);
          ????????????????}?catch?(NacosException?e)?{
          ????????????????????exception?=?e;
          ????????????????????NAMING_LOGGER.error("request?{}?failed.",?server,?e);
          ????????????????}?catch?(Exception?e)?{
          ????????????????????exception?=?e;
          ????????????????????NAMING_LOGGER.error("request?{}?failed.",?server,?e);
          ????????????????}
          ???????????????//輪詢
          ????????????????index?=?(index?+?1)?%?servers.size();
          ????????????}
          ???????//?..........
          }

          最后通過(guò) callServer(api, params, server, method) 發(fā)起調(diào)用,這里通過(guò) JSK自帶的 HttpURLConnection 進(jìn)行發(fā)起調(diào)用。我們可以通過(guò)斷點(diǎn)的方式來(lái)看到這里的請(qǐng)求參數(shù):

          HttpURLConnection 進(jìn)行發(fā)起調(diào)用

          期間可能會(huì)有多個(gè) GET 的請(qǐng)求獲取服務(wù)列表,是正常的,會(huì)發(fā)現(xiàn)有如上的一個(gè)請(qǐng)求,會(huì)調(diào)用 http://192.168.200.1:8848/nacos/v1/ns/instance 這個(gè)地址。那么接下去就是Nacos Server 接受到服務(wù)端的注冊(cè)請(qǐng)求的處理流程。需要下載Nacos Server 源碼,源碼下載可以參考官方文檔,本文不做過(guò)多闡述。

          「Nacos服務(wù)端的處理:」

          服務(wù)端提供了一個(gè)InstanceController類,在這個(gè)類中提供了服務(wù)注冊(cè)相關(guān)的API,而服務(wù)端發(fā)起注冊(cè)時(shí),調(diào)用的接口是:[post]: /nacos/v1/ns/instance ,serviceName: 代表客戶端的項(xiàng)目名稱 ,namespace: nacos 的namespace。

          @CanDistro
          @PostMapping
          @Secured(parser?=?NamingResourceParser.class,?action?=?ActionTypes.WRITE)
          public?String?register(HttpServletRequest?request)?throws?Exception?{
          ????????
          ????????final?String?serviceName?=?WebUtils.required(request,?CommonParams.SERVICE_NAME);
          ????????final?String?namespaceId?=?WebUtils
          ????????????????.optional(request,?CommonParams.NAMESPACE_ID,?Constants.DEFAULT_NAMESPACE_ID);
          ????????//?從請(qǐng)求中解析出instance實(shí)例
          ????????final?Instance?instance?=?parseInstance(request);
          ????????
          ????????serviceManager.registerInstance(namespaceId,?serviceName,?instance);
          ????????return?"ok";
          }

          然后調(diào)用 ServiceManager 進(jìn)行服務(wù)的注冊(cè)

          public?void?registerInstance(String?namespaceId,?String?serviceName,?Instance?instance)?throws?NacosException?{
          ????????//創(chuàng)建一個(gè)空服務(wù),在Nacos控制臺(tái)服務(wù)列表展示的服務(wù)信息,實(shí)際上是初始化一個(gè)serviceMap,它是一個(gè)ConcurrentHashMap集合
          ????????createEmptyService(namespaceId,?serviceName,?instance.isEphemeral());
          ????????//從serviceMap中,根據(jù)namespaceId和serviceName得到一個(gè)服務(wù)對(duì)象
          ????????Service?service?=?getService(namespaceId,?serviceName);
          ????????
          ????????if?(service?==?null)?{
          ????????????throw?new?NacosException(NacosException.INVALID_PARAM,
          ????????????????????"service?not?found,?namespace:?"?+?namespaceId?+?",?service:?"?+?serviceName);
          ????????}
          ????????//調(diào)用addInstance創(chuàng)建一個(gè)服務(wù)實(shí)例
          ????????addInstance(namespaceId,?serviceName,?instance.isEphemeral(),?instance);
          }

          在創(chuàng)建空的服務(wù)實(shí)例的時(shí)候我們發(fā)現(xiàn)了存儲(chǔ)實(shí)例的map:

          public?void?createServiceIfAbsent(String?namespaceId,?String?serviceName,?boolean?local,?Cluster?cluster)
          ????????????throws?NacosException?
          {
          ????????//從serviceMap中獲取服務(wù)對(duì)象
          ????????Service?service?=?getService(namespaceId,?serviceName);
          ????????if?(service?==?null)?{//如果為空。則初始化
          ??????Loggers.SRV\_LOG.info("creating?empty?service?{}:{}",?namespaceId,?serviceName);
          ??????service?=?new?Service();
          ??????service.setName(serviceName);
          ??????service.setNamespaceId(namespaceId);
          ??????service.setGroupName(NamingUtils.getGroupName(serviceName));
          ??????//?now?validate?the?service.?if?failed,?exception?will?be?thrown
          ??????service.setLastModifiedMillis(System.currentTimeMillis());
          ??????service.recalculateChecksum();
          ??????if?(cluster?!=?null)?{
          ??????????cluster.setService(service);
          ??????????service.getClusterMap().put(cluster.getName(),?cluster);
          ??????}
          ??????service.validate();
          ??????putServiceAndInit(service);
          ??????if?(!local)?{
          ??????????addOrReplaceService(service);
          ??????}
          }

          在 getService 方法中我們發(fā)現(xiàn)了Map:

          /*
          *?Map(namespace,?Map(group::serviceName,?Service)).
          */

          private?final?Map>?serviceMap?=?new?ConcurrentHashMap<>();

          通過(guò)注釋我們可以知道,Nacos是通過(guò)不同的 namespace 來(lái)維護(hù)服務(wù)的,而每個(gè)namespace下有不同的group,不同的group下才有對(duì)應(yīng)的Service ,再通過(guò)這個(gè) serviceName 來(lái)確定服務(wù)實(shí)例。

          第一次進(jìn)來(lái)則會(huì)進(jìn)入初始化,初始化完會(huì)調(diào)用?putServiceAndInit

          private?void?putServiceAndInit(Service?service)?throws?NacosException?{
          ????????putService(service);//把服務(wù)信息保存到serviceMap集合
          ????????service.init();//建立心跳檢測(cè)機(jī)制
          ????????//實(shí)現(xiàn)數(shù)據(jù)一致性監(jiān)聽(tīng),ephemeral(標(biāo)識(shí)服務(wù)是否為臨時(shí)服務(wù),默認(rèn)是持久化的,也就是true)=true表示采用raft協(xié)議,false表示采用Distro
          ????????consistencyService
          ????????????????.listen(KeyBuilder.buildInstanceListKey(service.getNamespaceId(),?service.getName(),?true),?service);
          ????????consistencyService
          ????????????????.listen(KeyBuilder.buildInstanceListKey(service.getNamespaceId(),?service.getName(),?false),?service);
          ????????Loggers.SRV_LOG.info("[NEW-SERVICE]?{}",?service.toJson());
          ????}

          獲取到服務(wù)以后把服務(wù)實(shí)例添加到集合中,然后基于一致性協(xié)議進(jìn)行數(shù)據(jù)的同步。然后調(diào)用 addInstance

          public?void?addInstance(String?namespaceId,?String?serviceName,?boolean?ephemeral,?Instance...?ips)
          ????????????throws?NacosException?
          {
          ????????//?組裝key
          ????????String?key?=?KeyBuilder.buildInstanceListKey(namespaceId,?serviceName,?ephemeral);
          ????????//?獲取剛剛組裝的服務(wù)
          ????????Service?service?=?getService(namespaceId,?serviceName);
          ????????
          ????????synchronized?(service)?{
          ????????????List?instanceList?=?addIpAddresses(service,?ephemeral,?ips);
          ????????????
          ????????????Instances?instances?=?new?Instances();
          ????????????instances.setInstanceList(instanceList);
          ????????????//?也就是上一步實(shí)現(xiàn)監(jiān)聽(tīng)的類里添加注冊(cè)服務(wù)
          ????????????consistencyService.put(key,?instances);
          ????????}
          ????}

          然后給服務(wù)注冊(cè)方發(fā)送注冊(cè)成功的響應(yīng),結(jié)束服務(wù)注冊(cè)流程。以上內(nèi)容,希望大家有一個(gè)大概的認(rèn)識(shí),收藏起來(lái),后面慢慢多看幾次,牢記心中,面試中肯定是加分項(xiàng)。

          瀏覽 44
          點(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>
                  欧美日韩在线观看123 | 日韩特一级 | 亚洲精品久久久久 | 国产精品主播视频 | 97色噜噜视频 |