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

相关文章

Spring Boot集成/输出/日志级别控制/持久化开发实践

《SpringBoot集成/输出/日志级别控制/持久化开发实践》SpringBoot默认集成Logback,支持灵活日志级别配置(INFO/DEBUG等),输出包含时间戳、级别、类名等信息,并可通过... 目录一、日志概述1.1、Spring Boot日志简介1.2、日志框架与默认配置1.3、日志的核心作用

Python使用Tenacity一行代码实现自动重试详解

《Python使用Tenacity一行代码实现自动重试详解》tenacity是一个专为Python设计的通用重试库,它的核心理念就是用简单、清晰的方式,为任何可能失败的操作添加重试能力,下面我们就来看... 目录一切始于一个简单的 API 调用Tenacity 入门:一行代码实现优雅重试精细控制:让重试按我

破茧 JDBC:MyBatis 在 Spring Boot 中的轻量实践指南

《破茧JDBC:MyBatis在SpringBoot中的轻量实践指南》MyBatis是持久层框架,简化JDBC开发,通过接口+XML/注解实现数据访问,动态代理生成实现类,支持增删改查及参数... 目录一、什么是 MyBATis二、 MyBatis 入门2.1、创建项目2.2、配置数据库连接字符串2.3、入

Springboot项目启动失败提示找不到dao类的解决

《Springboot项目启动失败提示找不到dao类的解决》SpringBoot启动失败,因ProductServiceImpl未正确注入ProductDao,原因:Dao未注册为Bean,解决:在启... 目录错误描述原因解决方法总结***************************APPLICA编

深度解析Spring Security 中的 SecurityFilterChain核心功能

《深度解析SpringSecurity中的SecurityFilterChain核心功能》SecurityFilterChain通过组件化配置、类型安全路径匹配、多链协同三大特性,重构了Spri... 目录Spring Security 中的SecurityFilterChain深度解析一、Security

Redis客户端连接机制的实现方案

《Redis客户端连接机制的实现方案》本文主要介绍了Redis客户端连接机制的实现方案,包括事件驱动模型、非阻塞I/O处理、连接池应用及配置优化,具有一定的参考价值,感兴趣的可以了解一下... 目录1. Redis连接模型概述2. 连接建立过程详解2.1 连php接初始化流程2.2 关键配置参数3. 最大连

SpringBoot多环境配置数据读取方式

《SpringBoot多环境配置数据读取方式》SpringBoot通过环境隔离机制,支持properties/yaml/yml多格式配置,结合@Value、Environment和@Configura... 目录一、多环境配置的核心思路二、3种配置文件格式详解2.1 properties格式(传统格式)1.

Apache Ignite 与 Spring Boot 集成详细指南

《ApacheIgnite与SpringBoot集成详细指南》ApacheIgnite官方指南详解如何通过SpringBootStarter扩展实现自动配置,支持厚/轻客户端模式,简化Ign... 目录 一、背景:为什么需要这个集成? 二、两种集成方式(对应两种客户端模型) 三、方式一:自动配置 Thick

Python实现网格交易策略的过程

《Python实现网格交易策略的过程》本文讲解Python网格交易策略,利用ccxt获取加密货币数据及backtrader回测,通过设定网格节点,低买高卖获利,适合震荡行情,下面跟我一起看看我们的第一... 网格交易是一种经典的量化交易策略,其核心思想是在价格上下预设多个“网格”,当价格触发特定网格时执行买

Python标准库之数据压缩和存档的应用详解

《Python标准库之数据压缩和存档的应用详解》在数据处理与存储领域,压缩和存档是提升效率的关键技术,Python标准库提供了一套完整的工具链,下面小编就来和大家简单介绍一下吧... 目录一、核心模块架构与设计哲学二、关键模块深度解析1.tarfile:专业级归档工具2.zipfile:跨平台归档首选3.