Nacos核心功能点
服务注册
-
Nacos Client会通过发送REST请求的方式向Nacos Server注册自己的服务,提供自身的元数据,比如ip地址、端口等信息。Nacos Server接收到注册请求后,就会把这些元数据信息存储在一个双层的内存Map中。
-
Nacos 服务注册需要倒入的关键Jar。spring-cloud-start-alibaba-nacos-discovery,启动时候通过自动装配会加载spring.factories中的配置bean信息
-
关键的Bean: NacosServiceRegistryAutoConfiguration – >nacosAutoServiceRegistration 方法
-
入上nacosAutoServiceRegistration 实现了ApplicationListener,那么IOC容器初始化时候必然会执行onApplicationEvent
-
最终执行NacosServiceRegistry中register方法注册实例,流程如下:
- 总结:
- 我们在服务注册之前,需要倒入spring-clod-starter-alibaba-nacos-discovery,
- 通过springboot自动装配机制,他会优先区加载jar包地下的spring.factories中的配置类
- 其中关键的类是NacosServiceRegisterAutoConfiguration,在其中有一个关键的自动注册方法,NacosAutoServiceRegistration
- NacosAutoServiceRegistration的父类是一个实现了ApplicationListener接口的抽象类,在SpringIOC加载bean初始化Bean过程中,会去执行改注册类中定义的onApplicationEvent
- 因此在Bean初始化的阶段中完成了对服务的注册功能,在onApplicationEvent方法中实现的start方法,会启动服务注册流程
- 首先构建服务注册信息,ip,端口等重要元素,通过NamingService.registerInstance方法执行注册逻辑
- 注册之前首先会封装一个BeatInfo心跳检测信息,同时会将之前维护的BeanInfoMap中通名的心跳维护数据设置为stop状态,接着将新的BeanInfo注册到定时任务中,默认每5s发送一次心跳给NacosService端
- 接着通过NacosServer提供的rest接口获取NacosSserver服务器列表,通过轮询+随机的方式将本服务注册到对应的NacosServer上
- NacosServer通过 自身的服务同步功能完成其他nacosService上节点信息的注册,至此完成所有服务注册的流程
服务心跳
-
在服务注册后,Nacos Client会维护一个定时心跳来持续通知Nacos Server,说明服务一直处于可用状态,防止被剔除。默认5s发送一次心跳。
-
服务启动时候会通过NamingServic.registrInstance 注册服务,注册之前首先会注册心跳信息。
-
总结:
- 在服务注册的时候,客户端在服务注册的时候首先会生成服务注册信息 + 注册心跳信息
- 在服务注册开始之前,会构建一个BeatInfo对象,这个是本服务的心跳信息维护 其中包括了基础的服务信息 IP,port,权重,服务名
- 在BeatInfo定义后,会将之前在BeatReact的所有客户端心跳对象的ConcurrentHashMap中查找是否有当前的BeatInfo,如果有,则获取到之前的BeatInfo并且设置为stop状态,让后在将当前的BeatInfo信息放入到BeatReact的ConcurrentHashMap中
- 接着通过BeatInfo封装成BeatTask并且注册到定时任务中,默认5秒执行一次
- BeatTask中的run方法封装了对NacosServer中心跳rest接口的请求逻辑。
服务健康检查
-
Nacos Server会开启一个定时任务用来检查注册服务实例的健康情况,对于超过15s没有收到客户端心跳的实例会将它的healthy属性置为false(客户端服务发现时不会发现),如果某个实例超过30秒没有收到心跳,直接剔除该实例(被剔除的实例如果恢复发送心跳则会重新注册)
- nacos 1.X版本中临时实例是通过客户端向注册中心发送心跳来维持healthy状态,持久化实例是走的Raft协议持久化存储,服务会定时与客户端建立tcp连接做健康检查
- nacox 2.X之后,持久化实例没有变化,但是临时实例不再使用心跳,而是通过长连接是否存活来判断是否健康。
-
Nacos Server长连接
- 在ConnectionManager中通ConcurrentHashMap来管理客户端长连接 key就是ip
- 在服务被踢出之前,Nacos Server 每隔3s 会对这批超过20S 都没有过通信的客户端发起探测请求,如果1s内能成功响应,则检测通过将不会被剔除,重新标记为可用状态,否则执行unregister方法移除connection
- NacosService 对于临时实例, 采用长连接 。 然后就是定时任务检查没通讯的客户端,防止长连接中断导致未及时剔除。
服务发现
- 服务消费者(Nacos Client)在调用服务提供者的服务时,会发送一个REST请求给Nacos Server,获取上面注册的服务清单,并且缓存在Nacos Client本地,同时会在Nacos Client本地开启一个定时任务定时拉取服务端最新的注册表信息更新到本地缓存
- 服务发现接口定义在NamingService中的getAllInstance,有订阅模式与,非订阅模式,订阅模式先查缓存没有才会请求,非订阅模式直接请求
- 订阅模式如下:通过NamingClientProxyDelegate,方法中subscribe,先启动定时任务,在判断缓存,没有则发起grpc
- 在开启定时任务时候,Nacos Client中会维护一个HostReactor,其中定义了一个UpdateTask,功能是实现本地列表的更新
- HostReactor中UpdateTask 每6s发送一次pull请求,获取新的地址列表
- 对于Nacos server 服务端,他也和每个服务提供者实例直接维护了长连接嗅探,一旦发现提供者出现异常,则发送push消息给Nacos客户端,也就是服务消费者
- 服务消费者,收到push过来的信息后,在HostReactor提供的processServiceJson中进行解析,更新缓存
- 总结:
- 服务发现方式有两种模式,一种订阅模式,一种非订阅模式,这两种方法都是在NamingSerevice.geteAllInstances中,
- 非订阅模式中 会直接通过NacosServer中定义的rest接口获取服务信息
- 订阅模式中,首先会注册一个定时任务,在HostReact中持有一个本地ConcurrentHashMap,接着通过封装一个UpdateTask,这个任务定义了每6秒间隔的时间从NacosSrver中获取对应的服务列表信息
- UpdateTask会通过rest接口向服务器发送一个pull的请求,在服务器收到请求后,通过UDP协议将变化后的信息push到对应的client端
- client端在HostReacter中的ProcessServiceJson方法中解析push过来的json对象来完成对HostReact中本地ConcurrentHashMap的一个更新操作
- 在完成定时任务设置后,才会去请求nacosServer获取列表rest接口,至此整个服务发现过程结束
Nacos 客户端本地缓存以及故障转移
-
Nacos本地缓存故障转移主要类:ServiceInfoHolder(本地缓存),FailoverReactor(故障转移)
- 本地缓存有两方面:第一从注册中心获取实例信息缓存在本地Map,第二通过定时任务将本地map中的serverinfo信息定时存储到磁盘中
- 故障转移也是两点:第一故障转移开关是通过文件来标记,第二开启故障转移后,发生故障时候,可以从故障备份文件中获取服务实例信息
-
ServiceInfoHodler 与 FailOverReactor两个对象相互持有,并且都有一个ServerInfo的ConcurrentHashMap,当首次启动初始化时,ServerInfoHolder会更具配置信息来加载本地文件中定义的ServerInfo信息
-
接着会开启FailOverReacotr中三个定时任务
- 初始化缓存每5s执行一次,将指定目录下记录的ServerInfo信息加载到FailOverReactor持有的ConcurrentHashMap中
- 初始化延迟30分钟,执行间隔24小时,将ServerInfoHolder 中持有的ConcurrentHashMap中ServerInfo写入到指定文件
- 初始化立即执行,间隔10s同样是将ServerInfoHolder中持有的ConcurrentHashMap中ServerInfo写入到指定文件
服务同步
-
Nacos Server集群之间会互相同步服务实例,用来保证服务信息的一致性。
-
当我们注册服务到NacosServer时候,会触发一个ClientChangedEvent 事件,通过这个事件完成对Nacos集群数据的同步。
-
其中被注册的Nacos节点称为负责同步节点, 其他节点是非负责同步节点
- 负责节点通过 DistroClientDataProcessor 同步数据到其他节点
- 非负责节点通过DistroClientDataProcessor.processData 接收数据并处理
-
Distro协议负责集群数据统一(1.x与2.x不同)
- 1.x版本中,责任节点每5s同步所有Server的instance列表的摘要(MD5)给非责任节点,非责任节点用收到的MD5对比本地MD5,发生变化则反查责任节点
- 2.x版本,责任节点发送每5s发送Client全量数据,非责任节点定时检测同步过来的Client是否过期,减少了1.x版本中的反查
-
Distro协议遵循三个原则
- 当节点收到自己负责的实例的写入请求时候,则直接写入
- 当节点收到非自己负责的实例的写入请求时候,则将写入信息在Nacos集群内部路由找到对应的负责节点后在执行写入操作
- 当节点收到任何实例的读取请求的时候,直接在本机nacos缓存中读取。
Nacos 平滑升级 2.0 以上
- 启动新版本的nacos
- 将新版本的nacos服务器加入到集群中
- 在nacos控制中的负载均器中配置接口访问权重,将久的nacos中的节点信息逐步配置成0,让节点逐步向新nacos来反问注册
- 等待老版本服务下线
Nacos 中注册的服务平滑上下线方案
-
方案一:
- 基于nacos控制台中的上线,下线功能
-
升级步骤如下:
- 系统升级之前在nacos的后台将要升级服务下线一台
- 新服务上线一台,等新服务注册到nacos中完成一台服务平滑升级
- 接着关闭一家下线的这一台的机器中的对应服务
- 为每个服务的多个实例,重复以上步骤
-
缺点:
- 下线后服务其实依然存在,只是负载均衡器过滤而已,可以通过IP访问
- 存在时间窗口,下线后ribbon中缓存没有更新,可以配置更新时间缩短窗口时间。
-
方案二:
- 基于Nacos后台提供的权重功能,可以在后台配置每一台的一个流量权重。
-
升级步骤:
- 升级前,将目标服务的一台机权重设置为0,流量被截断,请求不会被分发到这个实例
- 部署新的,等他注册到nacos,下线之前设置权重的老实例
- 将新注册的实例权重设置为正常值 1。
- 为每一个老的实例重复以上步骤
-
优点:
- 没有时间窗口问题