【转存学习】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

相关文章

使用Python实现IP地址和端口状态检测与监控

《使用Python实现IP地址和端口状态检测与监控》在网络运维和服务器管理中,IP地址和端口的可用性监控是保障业务连续性的基础需求,本文将带你用Python从零打造一个高可用IP监控系统,感兴趣的小伙... 目录概述:为什么需要IP监控系统使用步骤说明1. 环境准备2. 系统部署3. 核心功能配置系统效果展

MySQL中的分组和多表连接详解

《MySQL中的分组和多表连接详解》:本文主要介绍MySQL中的分组和多表连接的相关操作,本文通过实例代码给大家介绍的非常详细,感兴趣的朋友一起看看吧... 目录mysql中的分组和多表连接一、MySQL的分组(group javascriptby )二、多表连接(表连接会产生大量的数据垃圾)MySQL中的

Java 实用工具类Spring 的 AnnotationUtils详解

《Java实用工具类Spring的AnnotationUtils详解》Spring框架提供了一个强大的注解工具类org.springframework.core.annotation.Annot... 目录前言一、AnnotationUtils 的常用方法二、常见应用场景三、与 JDK 原生注解 API 的

Java controller接口出入参时间序列化转换操作方法(两种)

《Javacontroller接口出入参时间序列化转换操作方法(两种)》:本文主要介绍Javacontroller接口出入参时间序列化转换操作方法,本文给大家列举两种简单方法,感兴趣的朋友一起看... 目录方式一、使用注解方式二、统一配置场景:在controller编写的接口,在前后端交互过程中一般都会涉及

Java中的StringBuilder之如何高效构建字符串

《Java中的StringBuilder之如何高效构建字符串》本文将深入浅出地介绍StringBuilder的使用方法、性能优势以及相关字符串处理技术,结合代码示例帮助读者更好地理解和应用,希望对大家... 目录关键点什么是 StringBuilder?为什么需要 StringBuilder?如何使用 St

Python实现微信自动锁定工具

《Python实现微信自动锁定工具》在数字化办公时代,微信已成为职场沟通的重要工具,但临时离开时忘记锁屏可能导致敏感信息泄露,下面我们就来看看如何使用Python打造一个微信自动锁定工具吧... 目录引言:当微信隐私遇到自动化守护效果展示核心功能全景图技术亮点深度解析1. 无操作检测引擎2. 微信路径智能获

使用Java将各种数据写入Excel表格的操作示例

《使用Java将各种数据写入Excel表格的操作示例》在数据处理与管理领域,Excel凭借其强大的功能和广泛的应用,成为了数据存储与展示的重要工具,在Java开发过程中,常常需要将不同类型的数据,本文... 目录前言安装免费Java库1. 写入文本、或数值到 Excel单元格2. 写入数组到 Excel表格

redis中使用lua脚本的原理与基本使用详解

《redis中使用lua脚本的原理与基本使用详解》在Redis中使用Lua脚本可以实现原子性操作、减少网络开销以及提高执行效率,下面小编就来和大家详细介绍一下在redis中使用lua脚本的原理... 目录Redis 执行 Lua 脚本的原理基本使用方法使用EVAL命令执行 Lua 脚本使用EVALSHA命令

Java并发编程之如何优雅关闭钩子Shutdown Hook

《Java并发编程之如何优雅关闭钩子ShutdownHook》这篇文章主要为大家详细介绍了Java如何实现优雅关闭钩子ShutdownHook,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起... 目录关闭钩子简介关闭钩子应用场景数据库连接实战演示使用关闭钩子的注意事项开源框架中的关闭钩子机制1.

Python中pywin32 常用窗口操作的实现

《Python中pywin32常用窗口操作的实现》本文主要介绍了Python中pywin32常用窗口操作的实现,pywin32主要的作用是供Python开发者快速调用WindowsAPI的一个... 目录获取窗口句柄获取最前端窗口句柄获取指定坐标处的窗口根据窗口的完整标题匹配获取句柄根据窗口的类别匹配获取句