一、Nacos 功能介绍
在微服务架构下,一个业务服务会被拆分成多个微服务,各个服务之间相互通信完成整体的功能。另外,为了避免单点故障,微服务都会采取集群方式的高可用部署,集群规模越大,性能也会越高,如图5-1所示
服务消费者要去调用多个服务提供者组成的集群。首先,服务消费者需要在本地配置文件中维护服务提供者集群的每个节点的请求地址。其次,服务提供者集群中如果某个节点下线或者宕机,服务消费者的本地配置中需要同步删除这个节点的请求地址,防止请求发送到已宕机的节点上造成请求失败。为了解决这类的问题,就需要引入服务注册中心,它主要有以下功能:
- 服务地址的管理
- 服务注册。
- 服务动态感知
、
能够实现这类功能的组件很多,比如ZooKeeper、Eureka、Consul、Etcd、Nacos等ZooKeeper在第4章中介绍过,在这一章中主要介绍Alibaba的Nacos。
Nacos致力于解决微服务中的统一配置、服务注册与发现等问题。它提供了一组简单易用的特性集,帮助开发者快速实现动态服务发现、服务配置、服务元数据及流量管理
- 服务发现和服务健康监测
Nacos支持基于DNS和基于RPC的服务发现。服务提供者使用原生SDK、OpenAPI或一个独立的AgentTODO注册Service后,服务消费者可以使用DNS或HTTP&API查找和发现服务。
Nacos提供对服务的实时的健康检查,阻止向不健康的主机或服务实例发送请求。Nacos支持传输层(PING或TCP)和应用层(如HTTP、MySQL、用户自定义)的健康检查。对于复杂的云环境和网络拓扑环境中(如VPC、边缘网络等)服务的健康检查,Nacos提供了agent上报和服务端主动检测两种健康检查模式。Nacos还提供了统一的健康检查仪表盘,帮助用户根据健康状态管理服务的可用性及流量。 - 动态配置服务
业务服务一般都会维护一个本地配置文件,然后把一些常量配置到这个文件中,这种方式在某些场景中会存在问题,比如配置需要变更时要重新部署应用。而动态配置服务可以以中心化、外部化和动态化的方式管理所有环境的应用配置和服务配置,可以使配置管理变得更加高效和敏捷。配置中心化管理让实现无状态服务变得更简单,让服务按需弹性扩展变得更容易。
另外,Nacos提供了一个简洁易用的UI(控制台样例Demo)帮助用户管理所有服务和应用的配置。Nacos还提供了包括配置版本跟踪、金丝雀发布、一键回滚配置及客户端配置更新状态跟踪在内的一系列开箱即用的配置管理特性,帮助用户更安全地在生产环境中管理配置变更,降低配置变更带来的风险。 - 动态DNS服务
动态DNS服务支持权重路由,让开发者更容易地实现中间层负载均衡、更灵活的路由策略、流量控制,以及数据中心内网的简单DNS解析服务。 - 服务及其元数据管理
Nacos可以使开发者从微服务平台建设的视角管理数据中心的所有服务及元数据,包括管理服务的描述、生命周期、服务的静态依赖分析、服务的健康状态、服务的流量管理、路由及安全策略、服务的SLA及最重要的metrics统计数据。
本书主要围绕Nacos中注册中心的特性及动态配置服务的特性进行展开讲解。
二、 Nacos的基本使用
2.1Nacos的安装
2.2Nacos服务注册发现相关API说明
Nacos提供了SDK及OpenAPI的方式来完成服务注册与发现等操作,由于服务端只提供了REST接口,所以SDK本质上是对于HTTP请求的封装。下面简单列一下和服务注册相关的核心接口。2Nacos服务注册发现相关API说明
Nacos提供了SDK及OpenAPI的方式来完成服务注册与发现等操作,由于服务端只提供了REST接口,所以SDK本质上是对于HTTP请求的封装。下面简单列一下和服务注册相关的核心接口。
提供的接口有
-
注册实例
-
获取全部实例
-
监听服务
监听服务是指监听指定服务下的实例变化。在前面的分析中我们知道,客户端从Nacos Server上获取的实例必须是健康的,否则会造成客户端请求失败。监听服务机制可以让客户端及时感知服务提供者实例的变化。
2.3 Nacos与springboot 的集成注册发现
创建一个Spring Boot工程spring-boot-nacos-discovery。
2.4Nacos的高可用部署
在分布式架构中,任何中间件或者应用都不允许单点存在,所以开源组件一般都会自己支持高可用集群解决方案,如图5-2所示,Nacos提供了类似于ZooKeeper的集群架构,包含一个Leader节点和多个Follower节点。和ZooKeeper不同的是,它的数据一致性算法采用的是Raft,同样采用了该算法的中间件有Redis、 Sentinel的Leader选举、Etcd等
2.5 Dubbo使用Nacos实现注册中心
Dubbo可以支持多种注册中心,比如在前面章节中讲的ZooKeeper,以及Consul、Nacos等。本节主要讲解如何使用Nacos作为Dubbo服务的注册中心,为Dubbo提供服务注册与发现的功能,实现步骤如下。
- 创建一个普通Maven项目spring-boot-dubbo-nacos-sample,添加两个模块:nacos-sample-api和nacos-sample-provider。其中,nacos-sample-provider是一个Spring Boot工程。在nacos-sample-api中声明接口
- 在nacos-sample-provider中添加依赖
-
上述依赖包的简单说明如下- dubbo-spring-boot-starter,Dubbo的Starter组件,添加Dubbo依赖。
- onacos-discovery-spring-boot-starter,Nacos的Starter组件
- onacos-sample-api,接口定义类的依赖
- 创建HelloServiceImpl类,实现IHelloService接口
- 修改applicationproperties配置,仅将dubboregistry.address中配置的协议改成了nacos://1270.01:8848,基于Nacos协议。
- 运行Spring Boot启动类,注意需要声明DubboComponentScan。
注册在Nacos上的服务服务启动成功之后,访问Nacos控制台,进入“服务管理”一“服务列表”,如图5-4所示,可以看到所有
在图5-4所示界面中,在“操作”列单击“详情”,可以看到IHelloService下所有服务提供者的实例元数据,如图5-5所示
服务的消费过程和第4章中演示的例子没有太大的区别,不再重复讲解,大家可以基于前面的代码进行改造。
2.6 Spring Cloud Alibaba Nacos Discovery
Nacos作为Spring CloudAlibaba中服务注册与发现的核心组件可以很好地帮助开发者将服务自动注册到Nacos服务端,并且能够动态感知和刷新某个服务实例的服务列表。使用Spring CloudAlibaba NacosDiscovery可以基于Spring Cloud规范快速接入Nacos,实现服务注册与发现功能。
在本节中,我们通过将Spring CloudAlibaba Nacos Discovery集成到Spring CloudAlibaba Dubbo,完成服务注册与发现的功能。
2.6.1服务开发
创建一个普通的Maven项目spring-cloud-nacos-sample,在项目中添加两个模块:
- spring-cloud-nacos-sample-api,暴露服务接口,作为服务提供者及服务消费者的接口契约。
- spring-cloud-nacos-sample-provider,项目类型为Spring Cloud,它是接口的实现项目的创建方式和类型与前面所演示的步骤一样。为了避免大家在实践的时候出现错误,笔者会将完整的过程再讲一遍,服务提供方的操作步骤如下
- 在spring-cloud-nacos-sample-api项目中定义一个接口IHelloService。
- 在spring-cloud-nacos-sample-provier项目的pomxml文件中添加相关依赖包
下面对上述依赖包做一个简单的说明。- spring-cloud-starter : Spring Cloud核心包。
- spring-cloud-starter-dubbo : 入Spring Cloud Alibaba Dubbo。
- spring-cloud-dubbo-sample-api:API的接口声明。
- spring-cloud-alibaba-nacos-discovery:基于Nacos的服务注册与发现
需要注意的是,在笔者所使用的版本中,spring-cloud-starter传递依赖的spring-cloud-context版本为221RELEASE。这个版本的包存在兼容问题,会导致如下错误:
在spring-cloud-nacos-sample-provider中创建接口的实现类HelloServicelmpl,其中@Service是Dubbo服务的注解,表示当前服务会发布成一个远程服务。
- 在application.properties中提供Dubbo及Nacos的配置,用于声明Dubbo服务暴露的网络端口和协议,以及服务注册的地址信息,完整的配置如下。
以上配置的简单说明如下- dubboscan.base-packages:功能等同于@DubboComponentScan,指定Dubbo服务实现类的扫描包路径。
- dubbo.registry.address: Dubbo服务注册中心的配置地址,它的值spring-cloud://localhost表示挂载到SpringCloud注册中心,不配置的话会提示没有配置注册中心的错误。
- spring.cloudnacosdiscovery.server-addr:Nacos服务注册中心的地址
- 启动服务。
单击“详情”,会看到如图5-7所示的信息
基于Sprina Cloud Alibaba Nacos实现服务注册时元数据中发布的服务接是com.alibaba.coud.dubbo.service.DubboMetadataService。那么消费者要怎么去找到HelloService呢?别急,进入Nacos控制台的“配置列表”,可以看到如图5-8所示的配置信息。实际上这里把发布的接口信息存储到了配置中心,并且建立了映射关系,从而使得消费者在访问服务的时候能够找到目标接口进行调用。至此,服务端便全部开发完了,接下来我们开始消费端的开发。
2.6.2消费端开发
消费端开发很简单,操作步骤如下:
- 创建一个Spring Boot项目spring-cloud-nacos-consumer。
- 添加相关Maven依赖
上述依赖包和服务提供者的没什么区别,为了演示效果需要,增加了spring-boot-starter-web依赖。 - 在applicationproperties中添加配置信息
这些配置前面都讲过,就不重复解释了。 - 定义HelloController,用于测试Dubbo服务的访问
- 启动服务
与第4章中Dubbo SpringCloud的代码相比,除了注册中心从ZooKeeper变成Nacos,其他基本没什么变化,因为这两者都是基于SpringCloud标准实现的,而这些标准化的定义都抽象到了Spring-Cloud-Common包中。在后续的组件集成过程中,会以本节中创建的项目进行集成,希望各位读者读到这一节的时候把前面的代码都梳理一遍
2.7 Nacos实现原理分析
到目前为止,大家对于Nacos应该有了一定的认识。在本节中,我们主要通过Nacos的架构及实现注册中心的原理来进一步进行了解。
2.7.1 Nacos架构图
图5-9是Nacos官方提供的架构图,我们简单来分析一下它的模块组成;
- Provider APP:服务提供者
- ConsumerAPP:服务消费者
- Name Server:通过VIP(VritualIP)或者DNS的方式实现Nacos高可用集群的服务路由。
- Nacos Server:Nacos服务提供者,里面包含的pen API是功能访问入口,Config Service、Naming Service是Nacos提供的配置服务、名字服务模块。Consistency Protocol是一致性协议,用来实现Nacos集群节点的数据同步,这里使用的是Raft算法(使用类似算法的中间件还有Etcd、Redis哨兵选举)。
- Nacos Console:Nacos控制台
整体来说,服务提供者通过VIP(VirtualIP)访Nacos Server高可用集群基于OpenAPI完成服务的册和服务的查询。NacosServer本身可以支持主备模式,所以底层会采用数据一致性算法来完成从节点的数据同步。服务消费者也是如此,基于Open API从Nacos Server中查询服务列表。
2.7.2注册中心的原理
服务注册的功能主要体现在:
- 服务实例在启动时注册到服务注册表,并在关闭时注销。
- 服务消费者查询服务注册表,获得可用实例
- 服务注册中心需要调用服务实例的健康检查API来验证它是否能够处理请求Nacos服务注册与发现的实现原理如图5-10所示。
2.8 深入解读Nacos源码
Nacos源码部分,我们主要阅读三部分
- 服务注册
- 服务地址的获取
- 服务地址变化的感知。
下面我们基于这三个方面来分析Nacos是如何实现的
2.8.1Spring Cloud什么时候完成服务注册
在Spring-Cloud-Common包中有一个类orgspringframeworkcloud.clientserviceregistryServiceRegistry,它是Spring Cloud提供的服务注册的标准集成到Spring Cloud中实现服务注册的组件,都会实现该接口。
这个接口有一务注册动作的呢?
实现类是com.alibaba.cloud.nacos.registry.NacosServiceRegistry。它是什么时候触发服务注册动作的呢?
SpringCloud集成Nacos的实现过程
在spring-dloud-commons包的META-INF/spring.factories中包含自动装配的配置信息如下其中AutoServiceRegistrationAutoConfiguration就是服务注册相关的配置类,代码如下
在AutoServiceRegistrationAutoConfiguration配置类中,可以看到注入了一个AutoServiceRegistration实例,该类的关系图如图5-11所示可以看出,AbstractAutoServiceRegistration抽象类实现了该接口,并且最重要的是NacosAutoServiceRegistration继承了AbstractAutoServiceReqistration。
我们重点关注ApplicationListener,熟悉Spring的读者应该知道它是一种事件监听机制,该类的声明如下
public interface Applicationlistener<E extends ApplicationEvent> extends EventListener
void onApplicationEvent(E var1);
其中方法的作用是监听某个指定的事件。而AbstractAutoServiceRegistration实现了该抽象方法,并且监听WebServerInitializedEvent事件(当Webserver初始化完成之后),调用this.bind(event)方法
继续跟进this.bind方法,可以发现最终会调用NacosServiceRegistry.register方法进行服务注册
2.8.1.1Spring CloudAlibaba Dubbo集成Nacos的实现
Spring CloudAlibaba Dubbo集成Nacos时,服务的注册是依托Dubbo中的自动装配机制完成的。spring-cloud-alibaba-dubbo下的META-INF/spring.factories文件中自动装配了一个和服务注册相关的配置类DubboLoadBalancedRestTemplateAutoConfiguration
在该类中,有一个@EventListener的声明,它会监听ApplicationStartedEvent事件(Spring Boot 2.0新增的事件),该事件是在刷新上下文之后、调用application命令之前触发的
收到事件通知后,调用thisregister,最终仍然调用NacosServiceRegistry中的register方法实现服务的注册;
2.8.2 NacosServiceRegistry的实现
在NacosServiceRegistry.registry方法中,调用了Nacos Client SDK中的namingServiceregisterInstance完成服务的注册。
再来看一下namingServiceregisterInstance()方法的实现,主要逻辑如下。
- 通过beatReactor.addBeatInfo创建心跳信息实现健康检测,Nacos Server必须要确保注册的服务实例是健康的,而心跳检测就是服务健康检测的手段。
- serverProxy.registerService实现服务注册:
服务注册的逻辑在下一节中单独分析,这里重点关注beatReactor.addBeatInfo实现的心跳机制。很多读者都知道心跳机制,但是不太清楚该怎么实现,心跳机制的代码如下:
从上述代码看,所谓心跳机制就是客户端通过schedule定时向服务端发送一个数据包,然后启动一个线程不断检测服务端的回应,如果在设定时间内没有收到服务端的回应,则认为服务器出现了故障。Nacs服务端会根据客户端的心跳包不断更新服务的状态。
2.8.3从源码层面分析Nacos服务注册的原理
Nacos提供了SDK及Open API的形式来实现服务注册。前面我们通过Open API实现了一个服务地址的注册,其中serviceName表示服务名ip/port表示该服务对应的地址。
基于SDK形式实现服务注册的方法如下这两种形式的本质都一样,SDK方式只是提供了一种访问的封装,在底层仍然是基于HTTP协议完成请求的,所以我们直接基于OpenAPI请求方式来分析服务端的服务注册原理
注:源码分析的版本为1.14,大家可以在GitHub上搜索并下载
对于服务注册,对外提供的服务接口请求地址为nacos/v1/ns/instance,实现代码在nacos-naming模块下的InstanceController类中
- 从请求参数中获得serviceName(服务名)和namespaceId(命名空间Id)。
- 调用registerInstance注册实例。
下面简单分析一下createEmptyService创建空服务的代码,最终调用的方法是reateServiceIfAbsent. - 根据namspaceIdserviceName从缓存中获取Service实例
- 如果Service实例为空,则创建并保存到缓存中。
也没有太多的复杂逻辑,主要关注putServiceAndInit方法,它实现了以下功能
- 通过putService方法将服务缓存到内存
- serviceinit()建立心跳检测机制
- consistencyServicelisten实现数据一致性的监听。
serviceinit()方法的代码就不看了,如图5-12所示,它主要通过定时任务不断检测当前服务下所有实例最后发送心跳包的时间。如果超时,则设置healthy为false表示服务不健康,并且发送服务变更事件。在这里请大家思考一个问题,服务实例的最后心跳包更新时间是谁来触发的?实际上前面有讲到,Nacos客户端注册服务的同时也建立了心跳机制。
下面看putService方法,它的功能是将Service保存到serviceMap中。
上述步骤完成之后,继续调用addInstance方法把当前注册的服务实例保存到Service中。
至此,服务的注册基本上就完成了,可能各位读者会有点意犹未尽,实际上Alibaba下的各个组件都是可以独立写成一本书来介绍的。本书的主要目的还是讲解Spring CloudAlibaba生态,因此在源码部分基本会以核心功能为主进行引导,有兴趣的读者在阅读源码过程中遇到问题可以直接在GitHub上提问。
最后,简单总结一下服务注册的完整过程: - Nacos客户端通过OpenAPI的形式发送服务注册请求
- Nacos服务端收到请求后,做以下三件事
- 构建一个Service对象保存到ConcurrentHashMap集合中
- 使用定时任务对当前服务下的所有实例建立心跳检测机制
- 基于数据一致性协议将服务数据进行同步。
2.8.4揭秘服务提供者地址查询
了解了服务注册的原理之后,再来看服务地址查询功能就很容易理解了。查询服务列表有两种形式
基于OpenAPI形式:
找到Nacos-Naming模块的下InstanceController类
- 解析请求参数。
- 通过doSrvIPXT返回服务列表数据
doSrvIPXT方法比较长,我们主要看核心部分的逻辑。 - 根据namespaceId、serviceName获得Service实例。
- 从Service实例中基于srvIPs得到所有服务提供者的实例信息
- 遍历组装JSON字符串并返回
2.8.5分析Nacos服务地址动态感知原理
服务消费者不仅需要获得服务提供者的地址列表,还需要在服务实例出现异常时监听服务地址的变化。可以通过调用subscribe方法来实现监听,其中serviceName表示服务名EventListener表示监听到事件
服务动态感知的基本原理如图5-13所示,Nacos客户端有一个HostReactor类,它的功能是实现服务的动态更新,基本原理是:
- 客户端发起事件订阅后,在HostReactor中有一个UpdateTask线程,每10s发送一次Pull请求,获得服务端最新的地址列表。
- 对于服务端,它和服务提供者的实例之间维持了心跳检测,一旦服务提供者出现异常,则会发送一个Push消息给Nacos客户端,也就是服务消费者。
- 服务消费者收到请求之后,使用HostReactor中提供的processServiceJSON解析消息,并更新本地服务地址列表。
2.9 总结
通过详细分析Nacos的基本使用及源码,希望大家对Nacos有更深刻的理解。其中,很多有意思的设计思想笔者都通过图形的方式进行了表述,建议大家自己去GitHub上下载Nacos的源码,按照笔者分析源码的思路进一步解读。
服务注册与发现作为微服务架构中不可或缺的组件,市面上提供了非常多的解决方案,比如本章讲的Nacos,还有ZooKeeper、Etcd、Consul、Eureka等。我们至少要针对其中一种解决方案进行深入研究,因为不管是什么类型的组件,实现服务注册与发现功能的本质是一样的。