Nacos注册中心-服务注册设计思想
一、什么是Nacos?
Nacos 是构建以“服务”为中心的现代应用架构 (例如微服务范式、云原生范式) 的服务基础设施。
服务(Service)是 Nacos 世界的一等公民。Nacos 支持几乎所有主流类型的“服务”的发现、配置和管理。
二、Nacos架构![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/135b4702587dbc87e1876cae388d4678.png)
三、注册中心核心功能点
功能点 | 描述 |
---|---|
服务注册 | 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集群之间会互相同步服务实例,用来保证服务信息的一致性。 |
四、客户端-服务注册
1、客户端如何实现自动注册?
2、注册的时间节点?
1、引入pom文件
<!-- https://mvnrepository.com/artifact/com.alibaba.cloud/spring-cloud-starter-alibaba-nacos-discovery-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<version>2.2.5.RELEASE</version>
</dependency>
2、通过自动装配NacosServiceRegistryAutoConfiguration,实现启动服务后自动注册;
继承了AbstractAutoServiceRegistration,而AbstractAutoServiceRegistration实现了ApplicationListener,监听了一个webService启动完成后会发送的一个事件;
AbstractAutoServiceRegistration把注册的通用逻辑抽象了出来,包括创建监听事件,NacosServiceRegistry只需要实现register()方法即可;
看到这里模版模式和观察者模式的实践扑面而来~
五、服务端-注册服务
1、服务注册表的内存结构
Service:
Cluster:
key | 作用 |
---|---|
namespace | 一般作为环境隔离:dev、test、pre、prod |
group | 可做为项目隔离 |
cluster | 机房集群隔离:BJ、GZ、SH |
2、服务注册流程采用 异步-内存队列
spring加载distroConsistencyService的时候就加载进来了
服务注册-流程图:
为什么要使用异步-队列的方式?
1、注册流程很长,会影响到客户端的启动速度;
2、满足高并发的场景(官网的压测报告中,TPS能到13000);
异步的逻辑如果出了异常?如何补偿呢?
如果发生了,异常也就是服务没有注册进入注册表,这时候在客户端是有心跳任务的,当调用心跳任务的时候,会检查instance是否存在,如果不存在,会重新注册;
3、更新注册表采用CopyOnWrite设计思想
为什么不能直接更新注册表?:
注册表本身结构复杂,更新的步骤会非常多,如果只针对一份注册表去做修改,大概率是会发生问题的;
写入的是时候,会复制出一份副本,对副本更新后,再update原注册表;
效果:
提升了读写的并发,牺牲了一点点读的时效性,但是在注册服务这个应用场景我觉得是可以接受的;
问题:
选择写时复制的思想,如果是同时写入,会不会有并发覆盖的问题?
更新的时候才用的阻塞队列的方式,是一个个更新的,从根上避免了这种情况(非常巧妙~);
六、服务发现
Nacos-客户端提供了一个服务发现的接口,获取instances列表,并保存在本地;
com.alibaba.nacos.api.naming.NamingService#getAllInstances(java.lang.String, java.lang.String, java.util.List<java.lang.String>, boolean)
本地服务表结构:
调用http接口获取服务列表
如何维护nacos客户端的缓存服务map?
1、在客户端创建了一个定时任务,定时拉取
nacos客户端选择了一种嵌套调用延时任务的方式,其目的是为了在发生异常的时候动态增加下一次调用的时间;
2、Nacos服务端会主动发送UDP请求;
七、Nacos服务端-服务健康检查
服务注册后,会创建一个健康检查定时任务(每5s)
ClientBeatCheckTask
创建定时任务
健康检查的两个指标:
1、如果当前时间距离上一次心跳超过15s,服务会自动下线;
2、如果当前时间距离上一次心跳超过30s,服务会自动删除
3、在集群模式下,该健康服务定时任务只会有一台机器执行检查任务;
对服务名称hash取模,保证只有一台机器执行健康检查任务;
疑问:
1、services如果有一台挂了,用普通哈希算法是否会存在问题?
2、删除服务的时候,为什么要自己调自己的web接口?
总结
1、公用的一些jar包,需要注入spring的配置类,可以考虑用springboot-starter,使其开箱即用;
2、通用的代码逻辑可以考虑用模版模式进行抽象;
3、Nacos服务端-服务注册:使用异步+阻塞队列+CopyOnWrite提高并发读写能力;
4、Nacos服务发现:客户端定时拉取服务列表,服务端发生修改会发送UDP请求给服务端;
5、Nacos健康检查的内容,以及集群下保证只有一台机器执行检查逻辑;