【转存学习】SpringCloudAlibaba:Nacos 实现原理详解

本文主要是介绍【转存学习】SpringCloudAlibaba:Nacos 实现原理详解,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

SpringCloudAlibaba:Nacos 实现原理详解

来源:https://blog.csdn.net/cold___play/article/details/108032204

Nacos 架构

基本架构及概念

Provider APP:服务提供者

Consumer APP:服务消费者

Name Server:通过 VIP(Virtual IP)或 DNS 的方式实现 Nacos 高可用集群的服务路由

Nacos Server:Nacos 服务提供者,里面包含的 Open API 是功能访问入口,Conig Service、Naming Service 是 Nacos 提供的配置服务、命名服务模块。

Consitency Protocol 是一致性协议,用来实现 Nacos 集群节点的数据同步,这里使用的是 Raft 算法(Etcd、Redis 哨兵选举)

Nacos Console:控制台

注册中心的原理
  • 服务实例在启动时注册到服务注册表,并在关闭时注销
  • 服务消费者查询服务注册表,获得可用实例
  • 服务注册中心需要调用服务实例的健康检查 API 来验证它是否能够处理请求

img

Spring Cloud 完成注册的时机

在 spring-cloud-commons 包中有一个类org.springframework.cloud.client.serviceregistry.ServiceRegistry , 它是 Spring Cloud 提供的服务注册的标准。集成到 Spring Cloud 中实现服务注册的组件, 都会实现该接口。

img

该接口有一个实现类是NacoServiceRegistry

在这里插入图片描述

Spring Cloud 集成 Nacos 的实现过程:

在 spring-cloud-commons 包的 META-INF/spring.factories 中包含自动装配的配置信息如下:

在这里插入图片描述

其中 AutoServiceRegistrationAutoConfiguration 就是服务注册相关的配置类:

@Configuration(proxyBeanMethods = false
)
@Import({AutoServiceRegistrationConfiguration.class})
@ConditionalOnProperty(value = {"spring.cloud.service-registry.auto-registration.enabled"},matchIfMissing = true
)
public class AutoServiceRegistrationAutoConfiguration {@Autowired(required = false)private AutoServiceRegistration autoServiceRegistration;@Autowiredprivate AutoServiceRegistrationProperties properties;public AutoServiceRegistrationAutoConfiguration() {}@PostConstructprotected void init() {if (this.autoServiceRegistration == null && this.properties.isFailFast()) {throw new IllegalStateException("Auto Service Registration has been requested, but there is no AutoServiceRegistration bean");}}
}

AutoServiceRegistrationAutoConfiguration 配置类中, 可以看到注入了一个 AutoServiceRegistration 实例, 该类的关系图如下所示。

在这里插入图片描述

可以看出, AbstractAutoServiceRegistration 抽象类实现了该接口, 并且最重要的是 NacosAutoServiceRegistration 继承了 AbstractAutoServiceRegistration

看到 EventListener 我们就应该知道,Nacos 是通过 Spring 的事件机制继承到 SpringCloud 中去的

AbstractAutoServiceRegistration 实现了 onApplicationEvent 抽象方法, 并且监听 WebServerInitializedEvent 事件 (当 Webserver 初始化完成之后) , 调用 this.bind ( event ) 方法。

public void onApplicationEvent(WebServerInitializedEvent event) {this.bind(event);
}/** @deprecated */
@Deprecated
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());this.start();}
}

最终会调用 NacosServiceRegistry.register() 方法进行服务注册。

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();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);}}
}

NacosServiceRegistry 的实现

在 NacosServiceRegistry.registry 方法中, 调用了 Nacos Client SDK 中的 namingService.registerInstance 完成服务的注册。

在这里插入图片描述

跟踪 NacosNamingService 的 registerInstance() 方法:

public void registerInstance(String serviceName, String groupName, Instance instance) throws NacosException {String groupedServiceName = NamingUtils.getGroupedName(serviceName, groupName);if (instance.isEphemeral()) {BeatInfo beatInfo = this.beatReactor.buildBeatInfo(groupedServiceName, instance);this.beatReactor.addBeatInfo(groupedServiceName, beatInfo);}this.serverProxy.registerService(groupedServiceName, groupName, instance);
}
public BeatInfo buildBeatInfo(String groupedServiceName, Instance instance) {BeatInfo beatInfo = new BeatInfo();beatInfo.setServiceName(groupedServiceName);beatInfo.setIp(instance.getIp());beatInfo.setPort(instance.getPort());beatInfo.setCluster(instance.getClusterName());beatInfo.setWeight(instance.getWeight());beatInfo.setMetadata(instance.getMetadata());beatInfo.setScheduled(false);beatInfo.setPeriod(instance.getInstanceHeartBeatInterval());return beatInfo;
}

通过beatReactor.addBeatInfo()创建心跳信息实现健康检测, Nacos Server 必须要确保注册的服务实例是健康的, 而心跳检测就是服务健康检测的手段。

serverProxy.registerService()实现服务注册

心跳机制:

public void addBeatInfo(String serviceName, BeatInfo beatInfo) {LogUtils.NAMING_LOGGER.info("[BEAT] adding beat: {} to beat map.", beatInfo);String key = this.buildKey(serviceName, beatInfo.getIp(), beatInfo.getPort());BeatInfo existBeat = null;if ((existBeat = (BeatInfo)this.dom2Beat.remove(key)) != null) {existBeat.setStopped(true);}this.dom2Beat.put(key, beatInfo);this.executorService.schedule(new BeatReactor.BeatTask(beatInfo), beatInfo.getPeriod(), TimeUnit.MILLISECONDS);MetricsMonitor.getDom2BeatSizeMonitor().set((double)this.dom2Beat.size());
}

从上述代码看, 所谓心跳机制就是客户端通过 schedule 定时向服务端发送一个数据包 , 然后启动一个线程不断检测服务端的回应, 如果在设定时间内没有收到服务端的回应, 则认为服务器出现了故障。Nacos 服务端会根据客户端的心跳包不断更新服务的状态

注册原理:

Nacos 提供了 SDK 和 Open API 两种形式来实现服务注册。

Open API:

img

SDK:

public void registerInstance(String serviceName, String ip, int port) throws NacosException

这两种形式本质都一样,底层都是基于 HTTP 协议完成请求的。所以注册服务就是发送一个 HTTP 请求:

public void registerService(String serviceName, String groupName, Instance instance) throws NacosException {LogUtils.NAMING_LOGGER.info("[REGISTER-SERVICE] {} registering service {} with instance: {}", new Object[]{this.namespaceId, serviceName, instance});Map<String, String> params = new HashMap(16);params.put("namespaceId", this.namespaceId);params.put("serviceName", serviceName);params.put("groupName", groupName);params.put("clusterName", 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", JacksonUtils.toJson(instance.getMetadata()));this.reqApi(UtilAndComs.nacosUrlInstance, params, "POST");
}

对于 nacos 服务端,对外提供的服务接口请求地址为nacos/v1/ns/instance,实现代码咋 nacos-naming 模块下的 InstanceController 类中:

img

  • 从请求参数汇总获得 serviceName(服务名)和 namespaceId(命名空间 Id)
  • 调用 registerInstance 注册实例

img

  • 创建一个控服务(在 Nacos 控制台 “服务列表” 中展示的服务信息),实际上是初始化一个 serviceMap,它是一个 ConcurrentHashMap 集合
  • getService,从 serviceMap 中根据 namespaceId 和 serviceName 得到一个服务对象
  • 调用 addInstance 添加服务实例

img

img

  • 根据 namespaceId、serviceName 从缓存中获取 Service 实例
  • 如果 Service 实例为空,则创建并保存到缓存中

img

  • 通过 putService() 方法将服务缓存到内存
  • service.init() 建立心跳机制
  • consistencyService.listen 实现数据一致性监听

service.init ( ) 方法的如下图所示,它主要通过定时任务不断检测当前服务下所有实例最后发送心跳包的时间。如果超时, 则设置 healthy 为 false 表示服务不健康, 并且发送服务变更事件。在这里请大家思考一一个问题, 服务实例的最后心跳包更新时间是谁来触发的? 实际上前面有讲到, Nacos 客户端注册服务的同时也建立了心跳机制。

img

putService 方法,它的功能是将 Service 保存到 serviceMap 中:

img

继续调用 addInstance 方法把当前注册的服务实例保存到 Service 中:

img

总结:Nacos 客户端通过 Open API 的形式发送服务注册请求,Nacos 服务端收到请求后,做以下三件事:

  • 1.构建一个 Service 对象保存到 ConcurrentHashMap 集合中
  • 2.使用定时任务对当前服务下的所有实例建立心跳检测机制
  • 3.基于数据一致性协议服务数据进行同步
服务提供者地址查询

Open API:

img

SDK:

img

InstanceController 中的 list 方法:

img

  • 解析请求参数
  • 通过 doSrvIPXT 返回服务列表数据

img

img

  • 根据 namespaceId、serviceName 获得 Service 实例
  • 从 Service 实例中基于 srvIPs 得到所有服务提供者实例
  • 遍历组装 JSON 字符串并返回
Nacos 服务地址动态感知原理

可以通过 subscribe 方法来实现监听,其中 serviceName 表示服务名、EventListener 表示监听到的事件:

img

具体调用方式如下:

img

或者调用 selectInstance 方法,如果将 subscribe 属性设置为 true,会自动注册监听:

img

img

Nacos 客户端中有一个 HostReactor 类,它的功能是实现服务的动态更新,基本原理是:

客户端发起时间订阅后,在 HostReactor 中有一个 UpdateTask 线程,每 10s 发送一次 Pull 请求,获得服务端最新的地址列表

对于服务端,它和服务提供者的实例之间维持了心跳检测,一旦服务提供者出现异常,则会发送一个 Push 消息给 Nacos 客户端,也就是服务端消费者

服务消费者收到请求之后,使用 HostReactor 中提供的 processServiceJSON 解析消息,并更新本地服务地址列表。

这篇关于【转存学习】SpringCloudAlibaba:Nacos 实现原理详解的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/242517

相关文章

Java中流式并行操作parallelStream的原理和使用方法

《Java中流式并行操作parallelStream的原理和使用方法》本文详细介绍了Java中的并行流(parallelStream)的原理、正确使用方法以及在实际业务中的应用案例,并指出在使用并行流... 目录Java中流式并行操作parallelStream0. 问题的产生1. 什么是parallelS

MySQL数据库双机热备的配置方法详解

《MySQL数据库双机热备的配置方法详解》在企业级应用中,数据库的高可用性和数据的安全性是至关重要的,MySQL作为最流行的开源关系型数据库管理系统之一,提供了多种方式来实现高可用性,其中双机热备(M... 目录1. 环境准备1.1 安装mysql1.2 配置MySQL1.2.1 主服务器配置1.2.2 从

C++中unordered_set哈希集合的实现

《C++中unordered_set哈希集合的实现》std::unordered_set是C++标准库中的无序关联容器,基于哈希表实现,具有元素唯一性和无序性特点,本文就来详细的介绍一下unorder... 目录一、概述二、头文件与命名空间三、常用方法与示例1. 构造与析构2. 迭代器与遍历3. 容量相关4

Java中Redisson 的原理深度解析

《Java中Redisson的原理深度解析》Redisson是一个高性能的Redis客户端,它通过将Redis数据结构映射为Java对象和分布式对象,实现了在Java应用中方便地使用Redis,本文... 目录前言一、核心设计理念二、核心架构与通信层1. 基于 Netty 的异步非阻塞通信2. 编解码器三、

C++中悬垂引用(Dangling Reference) 的实现

《C++中悬垂引用(DanglingReference)的实现》C++中的悬垂引用指引用绑定的对象被销毁后引用仍存在的情况,会导致访问无效内存,下面就来详细的介绍一下产生的原因以及如何避免,感兴趣... 目录悬垂引用的产生原因1. 引用绑定到局部变量,变量超出作用域后销毁2. 引用绑定到动态分配的对象,对象

Linux kill正在执行的后台任务 kill进程组使用详解

《Linuxkill正在执行的后台任务kill进程组使用详解》文章介绍了两个脚本的功能和区别,以及执行这些脚本时遇到的进程管理问题,通过查看进程树、使用`kill`命令和`lsof`命令,分析了子... 目录零. 用到的命令一. 待执行的脚本二. 执行含子进程的脚本,并kill2.1 进程查看2.2 遇到的

MyBatis常用XML语法详解

《MyBatis常用XML语法详解》文章介绍了MyBatis常用XML语法,包括结果映射、查询语句、插入语句、更新语句、删除语句、动态SQL标签以及ehcache.xml文件的使用,感兴趣的朋友跟随小... 目录1、定义结果映射2、查询语句3、插入语句4、更新语句5、删除语句6、动态 SQL 标签7、ehc

SpringBoot基于注解实现数据库字段回填的完整方案

《SpringBoot基于注解实现数据库字段回填的完整方案》这篇文章主要为大家详细介绍了SpringBoot如何基于注解实现数据库字段回填的相关方法,文中的示例代码讲解详细,感兴趣的小伙伴可以了解... 目录数据库表pom.XMLRelationFieldRelationFieldMapping基础的一些代

一篇文章彻底搞懂macOS如何决定java环境

《一篇文章彻底搞懂macOS如何决定java环境》MacOS作为一个功能强大的操作系统,为开发者提供了丰富的开发工具和框架,下面:本文主要介绍macOS如何决定java环境的相关资料,文中通过代码... 目录方法一:使用 which命令方法二:使用 Java_home工具(Apple 官方推荐)那问题来了,

Java HashMap的底层实现原理深度解析

《JavaHashMap的底层实现原理深度解析》HashMap基于数组+链表+红黑树结构,通过哈希算法和扩容机制优化性能,负载因子与树化阈值平衡效率,是Java开发必备的高效数据结构,本文给大家介绍... 目录一、概述:HashMap的宏观结构二、核心数据结构解析1. 数组(桶数组)2. 链表节点(Node