深入理解AP架构Nacos注册原理

发表于:2023-1-19 09:21

字体: | 上一篇 | 下一篇 | 我要投稿

 作者:harmony    来源:得物技术

  1、Nacos简介
  Nacos是一款阿里巴巴开源用于管理分布式微服务的中间件,能够帮助开发人员快速实现动态服务发现、服务配置、服务元数据及流量管理等。这篇文章主要剖析一下Nacos作为注册中心时其服务注册与发现原理。
  2、为什么会需要Nacos
  Nacos作为注册中心是为了更好更方便的管理应用中的每一个服务,是各个分布式节点之间的纽带。其作为注册中心主要提供以下核心功能:
  服务注册与发现:动态的增减服务节点,服务节点增减后动态的通知服务消费者,不需要由消费者来更新配置。
  服务配置:动态修改服务配置,并将其推送到服务提供者和服务消费者而不需要重启服务。
  健康检查和服务摘除:主动的检查服务健康情况,对于宕机的服务将其摘除服务列表。
  3、分布式架构CAP理论
  CAP定理是分布式系统中最基础的原则,所以理解和掌握了CAP对系统架构的设计至关重要。分布式架构下所有系统不可能同时满足以下三点:Consisteny(一致性)、Availability(可用性)、Partition tolerance(分区容错性),CAP指明了任何分布式系统只能同时满足这三项中的两项。
  分布式系统肯定都要保证其容错性 ,那么可用性和一致性就只能选一个了。简单来说分布式系统的CAP理论就像你想买个新手机,这个手机不可能功能强大、便宜、又好看的,它最多只能满足两点的,要么功能强大便宜、要么功能强大好看、要么便宜好看,不可能同时满足三点。
  4、几种注册中心的区别
  注册中心在分布式应用中是经常用到的,也是必不可少的,那注册中心,又分为以下几种:Eureka、Zookeeper、Nacos等。这些注册中心最大的区别就是其基于AP架构还是CP架构,简单介绍一下:
  Zookeeper:用过或者了解过zk做注册中心的同学都知道,Zookeeper集群下一旦leader节点宕机了,在短时间内服务都不可通讯,因为它们在一定时间内follower进行选举来推出新的leader,因为在这段时间内,所有的服务通信将受到影响,而且leader选取时间比较长,需要花费几十秒甚至上百秒的时间,因此:可以理解为 Zookeeper是实现的CP,也就是将失去A(可用性)。
  Eureka:Eureka集群下每个节点之间都会定时发送心跳,定时同步数据,没有master/slave之分,是一个完全去中心化的架构。因此每个注册到Eureka下的实例都会定时同步ip,服务之间的调用也是根据Eureka拿到的缓存服务数据进行调用。若一台Eureka服务宕机,其他Eureka在一定时间内未感知到这台Eureka服务宕机,各个服务之间还是可以正常调用。Eureka的集群中,只要有一台Eureka还在,就能保证注册服务可用(保证可用性),只不过查到的信息可能不是最新的(不保证强一致性)。当数据出现不一致时,虽然A, B上的注册信息不完全相同,但每个Eureka节点依然能够正常对外提供服务,这会出现查询服务信息时如果请求A查不到,但请求B就能查到。如此保证了可用性但牺牲了一致性。
  Nacos:同时支持CP和AP架构,根据根据服务注册选择临时和永久来决定走AP模式还是CP模式。如果注册Nacos的client节点注册时ephemeral=true,那么Nacos集群对这个client节点的效果就是AP,采用distro协议实现;而注册Nacos的client节点注册时ephemeral=false,那么Nacos集群对这个节点的效果就是CP的,采用raft协议实现。
  本篇文章主要是深入研究一下Nacos基于AP架构微服务注册原理,由于篇幅有限基于CP架构的Nacos微服务注册下次再跟你们分析。
  5、Nacos服务注册与发现的原理
  1.微服务在启动将自己的服务注册到Nacos注册中心,同时发布http接口供其他系统调用,一般都是基于SpringMVC。
  2.服务消费者基于Feign调用服务提供者对外发布的接口,先对调用的本地接口加上注解@FeignClient,Feign会针对加了该注解的接口生成动态代理,服务消费者针对Feign生成的动态代理去调用方法时,会在底层生成Http协议格式的请求,类似 /stock/deduct? productId=100。
  3.Feign最终会调用Ribbon从本地的Nacos注册表的缓存里根据服务名取出服务提供在机器的列表,然后进行负载均衡并选择一台机器出来,对选出来的机器IP和端口拼接之前生成的url请求,生成调用的Http接口地址。
  6、Nacos核心功能点
  服务注册:Nacos Client会通过发送REST请求的方式向Nacos Server注册自己的服务,提供自身的元数据,比如ip地址、端口等信息。Nacos Server接收到注册请求后,就会把这些元数据信息存储在一个双层的内存Map中。
  服务心跳:在服务注册后,Nacos Client会维护一个定时心跳来持续通知Nacos Server,说明服务一直处于可用状态,防止被剔除。默认5s发送一次心跳。
  服务健康检查:Nacos Server会开启一个定时任务用来检查注册服务实例的健康情况,对于超过15s没有收到客户端心跳的实例会将它 的healthy属性置为false(客户端服务发现时不会发现),如果某个实例超过30秒没有收到心跳,直接剔除该实例(被剔除的实例如果恢复 发送心跳则会重新注册)
  服务发现:服务消费者(Nacos Client)在调用服务提供者的服务时,会发送一个REST请求给Nacos Server,获取上面注册的服务清 单,并且缓存在Nacos Client本地,同时会在Nacos Client本地开启一个定时任务定时拉取服务端最新的注册表信息更新到本地缓存
  服务同步:Nacos Server集群之间会互相同步服务实例,用来保证服务信息的一致性。
  7、Nacos源码分析
  看Nacos源码的不难发现,Nacos实际上就是一个基于Spring Boot的web应用,不管是服务注册还是发送心跳都是通过给Nacos服务端发送http请求实现的。下载并编译Nacos源码就不过多赘述了,首先需要搭建一个微服务作为Nacos的客户端。
  Nacos客户端注册
  Nacos客户端也是个Spring Boot项目,当客户端服务启动时Spring Boot项目启动时自动加载spring-cloud-starter-alibaba-nacos-discovery包的META-INF/spring.factories中包含自动装配的配置信息,并将文件中的类加载成bean放入Spring容器中,我们可以先看一下spring.factories文件:
  org.springframework.boot.autoconfigure.EnableAutoCnotallow=\
    com.alibaba.cloud.nacos.discovery.NacosDiscoveryAutoConfiguration,\
    com.alibaba.cloud.nacos.endpoint.NacosDiscoveryEndpointAutoConfiguration,\
    com.alibaba.cloud.nacos.registry.NacosServiceRegistryAutoConfiguration,\
    com.alibaba.cloud.nacos.discovery.NacosDiscoveryClientConfiguration,\
    com.alibaba.cloud.nacos.discovery.reactive.NacosReactiveDiscoveryClientConfiguration,\
    com.alibaba.cloud.nacos.discovery.configclient.NacosConfigServerAutoConfiguration,\
    com.alibaba.cloud.nacos.NacosServiceAutoConfiguration
  org.springframework.cloud.bootstrap.BootstrapCnotallow=\
    com.alibaba.cloud.nacos.discovery.configclient.NacosDiscoveryClientConfigServiceBootstrapConfiguration
  找到Nacos注册中心的自动配置类:NacosServiceRegistryAutoConfiguration。
  NacosServiceRegistryAutoConfiguration这个类是Nacos客户端启动时的一个入口类,代码如下:
  @Configuration(
      proxyBeanMethods = false
  )
  @EnableConfigurationProperties
  @ConditionalOnNacosDiscoveryEnabled
  @ConditionalOnProperty(
      value = {"spring.cloud.service-registry.auto-registration.enabled"},
      matchIfMissing = true
  )
  @AutoConfigureAfter({AutoServiceRegistrationConfiguration.class,
                       AutoServiceRegistrationAutoConfiguration.class, 
                       NacosDiscoveryAutoConfiguration.class})
  public class NacosServiceRegistryAutoConfiguration {
      public NacosServiceRegistryAutoConfiguration() {
      }
      @Bean
      public NacosServiceRegistry nacosServiceRegistry(NacosDiscoveryProperties nacosDiscoveryProperties) {
          return new NacosServiceRegistry(nacosDiscoveryProperties);
      }
      @Bean
      @ConditionalOnBean({AutoServiceRegistrationProperties.class})
      public NacosRegistration nacosRegistration(ObjectProvider<List<NacosRegistrationCustomizer>> registrationCustomizers, NacosDiscoveryProperties nacosDiscoveryProperties, ApplicationContext context) {
          return new NacosRegistration((List)registrationCustomizers.getIfAvailable(), nacosDiscoveryProperties, context);
      }
      @Bean
      @ConditionalOnBean({AutoServiceRegistrationProperties.class})
      public NacosAutoServiceRegistration nacosAutoServiceRegistration(NacosServiceRegistry registry, AutoServiceRegistrationProperties autoServiceRegistrationProperties, NacosRegistration registration) {
          return new NacosAutoServiceRegistration(registry, autoServiceRegistrationProperties, registration);
      }
  }
  看NacosServiceRegistryAutoConfiguration配置类有3个@Bean注解。
  nacosServiceRegistry()方法: 定义了NacosServiceRegistry的bean,并且为其属性nacosDiscoveryProperties赋值,即将从配置文件中读取到的配置信息赋值进去待用;
  nacosRegistration()方法主要就是定义了NacosRegistration的bean,后面会用到这个bean;
  nacosAutoServiceRegistration:该方法比较核心它的参数中有2个就是前面定义的两个bean,其实就是为了这个方法服务的,由NacosAutoServiceRegistration类的构造器传入NacosAutoServiceRegistration类中:NacosAutoServiceRegistration(registry, autoServiceRegistrationProperties, registration),后面的流程都是以这句代码作为入口。
  利用IDEA查看类结构,如上图所示,NacosAutoServiceRegistration继承AbstractAutoServiceRegistration类,而AbstractAutoServiceRegistration类又实现了AutoServiceRegistration和ApplicationListener接口。
  ApplicationListener接口是Spring提供的事件监听接口,Spring会在所有bean都初始化完成之后发布一个事件,ApplicationListener会监听所发布的事件,这里的事件是Spring Boot自定义的WebServerInitializedEvent事件,主要是项目启动时就会发布WebServerInitializedEvent事件,然后被AbstractAutoServiceRegistration监听到,从而就会执行onApplicationEvent方法,在这个方法里就会进行服务注册。
  这里AbstractAutoServiceRegistration类实现了Spring监听器接口ApplicationListener,并重写了该接口的onApplicationEvent方法。
  public void onApplicationEvent(WebServerInitializedEvent event) {
       this.bind(event);
  }
  继续点下去看bind方法。
  public void bind(WebServerInitializedEvent event) {
          ApplicationContext context = event.getApplicationContext();
          if (!(context instanceof ConfigurableWebServerApplicationContext) || !"management".equals(((ConfigurableWebServerApplicationContext)context).getServerNamespace())) {
              this.port.compareAndSet(0, event.getWebServer().getPort());
              //start方法
              this.start();
          }
      }
  看到这里发现了bind方法里有个非常重要的start()方法,继续看该方法的register()就是真正的客户端注册方法。
  public void start() {
          if (!this.isEnabled()) {
              if (logger.isDebugEnabled()) {
                  logger.debug("Discovery Lifecycle disabled. Not starting");
              }
          } else {
              if (!this.running.get()) {
                  this.context.publishEvent(new InstancePreRegisteredEvent(this, this.getRegistration()));
                  //真正的客户端注册方法
                  this.register();
                  if (this.shouldRegisterManagement()) {
                      this.registerManagement();
                  }
                  this.context.publishEvent(new InstanceRegisteredEvent(this, this.getConfiguration()));
                  this.running.compareAndSet(false, true);
              }
          }
      }
  跳过一些中间非关键性的代码,可以直接看该注册方法。
  protected void register() {
     this.serviceRegistry.register(getRegistration());
  }
  这里的serviceRegistry就是NacosServiceRegistryAutoConfiguration类中第一个@Bean定义的bean,第一个@Bean就是这里的serviceRegistry对象的实现;其中getRegistration()获取的就是第二个@Bean定义的NacosRegistration的实例,这两个bean实例都是通过第3个@Bean传进来的,所以这里就可以把NacosServiceRegistryAutoConfiguration类中那3个@Bean给串起来了。
  protected void register() {
     this.serviceRegistry.register(getRegistration());
  }
  不得不说,阿里巴巴开发的中间件,其底层源码的命名还是很规范的,register()方法从命名上来看就可以知道这是注册的方法,事实也确实是注册的方法,这个方法中会通过nacos-client包来调用nacos-server的服务注册接口来实现服务的注册功能。下面我看一下调用Nacos注册接口方法:
  public void register(Registration registration) {
          if (StringUtils.isEmpty(registration.getServiceId())) {
              log.warn("No service to register for nacos client...");
          } else {
              NamingService namingService = this.namingService();
              String serviceId = registration.getServiceId();
              String group = this.nacosDiscoveryProperties.getGroup();
              //构建客户端参数ip,端口号等
              Instance instance = this.getNacosInstanceFromRegistration(registration);
              try {
                  //调用注册方法
                  namingService.registerInstance(serviceId, group, instance);
                  log.info("nacos registry, {} {} {}:{} register finished", new Object[]{group, serviceId, instance.getIp(), instance.getPort()});
              } catch (Exception var7) {
                  log.error("nacos registry, {} register failed...{},", new Object[]{serviceId, registration.toString(), var7});
                  ReflectionUtils.rethrowRuntimeException(var7);
              }
          }
      }
          //构建客户端注册参数
      private Instance getNacosInstanceFromRegistration(Registration registration) {
          Instance instance = new Instance();
          instance.setIp(registration.getHost());
          instance.setPort(registration.getPort());
          instance.setWeight((double)this.nacosDiscoveryProperties.getWeight());
          instance.setClusterName(this.nacosDiscoveryProperties.getClusterName());
          instance.setEnabled(this.nacosDiscoveryProperties.isInstanceEnabled());
          instance.setMetadata(registration.getMetadata());
          instance.setEphemeral(this.nacosDiscoveryProperties.isEphemeral());
          return instance;
      }
  根据源码可以知道beatReactor.addBeatInfo()方法作用在于创建心跳信息实现健康检测,Nacos 服务端必须要确保注册的服务实例是健康的,而心跳检测就是服务健康检测的手段。而serverProxy.registerService()实现服务注册,综上可以分析出Nacos客户端注册流程:
  到此为止还没有真正的实现服务的注册,但是至少已经知道了Nacos客户端的自动注册原理是借助了Spring Boot的自动配置功能,在项目启动时通过自动配置类。NacosServiceRegistryAutoConfiguration将NacosServiceRegistry注入进来,通过Spring的事件监听机制,调用该类的注册方法register(registration)实现服务的自动注册。
  本文内容不用于商业目的,如涉及知识产权问题,请权利人联系51Testing小编(021-64471599-8017),我们将立即处理
《2023软件测试行业现状调查报告》独家发布~

关注51Testing

联系我们

快捷面板 站点地图 联系我们 广告服务 关于我们 站长统计 发展历程

法律顾问:上海兰迪律师事务所 项棋律师
版权所有 上海博为峰软件技术股份有限公司 Copyright©51testing.com 2003-2024
投诉及意见反馈:webmaster@51testing.com; 业务联系:service@51testing.com 021-64471599-8017

沪ICP备05003035号

沪公网安备 31010102002173号