【深入理解SpringCloud微服务】深入理解nacos

news2024/12/26 23:40:03

【深入理解SpringCloud微服务】深入理解nacos

  • Nacos服务注册
  • 内存注册表
  • 内存注册表的更新
  • 通知客户端服务变更、服务同步、健康检查
  • 2.x版本nacos的变化

Nacos服务注册

spring-cloud-alibaba-nacos-discovery通过实现spring-cloud-commons规范定义的接口,完成nacos接入spring-cloud后的客户端自动注册,我们不研究Nacos如何实现spring-cloud-commons规范,只要知道最终是利用Spring的事件监听机制触发服务注册

在这里插入图片描述

然后就会调用nacos的NamingService进行服务注册,所以真正的入口是这个NamingService

nacos总体上也是CS结构:
在这里插入图片描述

有一个nacos服务端接收http请求,然后我们的微服务集成了一个nacos客户端,发送http请求给nacos服务端,这个nacos客户端就是NamingService。通过Spring的事件监听机制触发NamingService的调用,而NamingService通过HttpURLConnection发送http请求。

在这里插入图片描述

AbstractAutoServiceRegistration#onApplicationEvent:

    public void onApplicationEvent(WebServerInitializedEvent event) {
        this.bind(event);
    }
    
    public void bind(WebServerInitializedEvent event) {
        ...
            this.start();
        ...
    }
	
    public void start() {
        ...
                this.register();
        ...
    }
    
    protected void register() {
        this.serviceRegistry.register(this.getRegistration());
    }

上面的this.serviceRegistry就是NacosServiceRegistry,this.getRegistration()返回的就是NacosRegistration,然后this.serviceRegistry.register(this.getRegistration())就会进入NacosServiceRegistry的register方法。

NacosServiceRegistry#register

	@Override
	public void register(Registration registration) {
			...
			namingService.registerInstance(serviceId, group, instance);
			...
	}

调用NamingService的registerInstance方法进行服务注册,里面就是向nacos服务端发起服务注册的http请求了。

然后nacos的InstanceController接收到http请求,调用ServiceManager更新内存注册表。

nacos使用了一个ServiceManager保存了内存注册表,当接收到服务注册请求时,nacos服务端会把服务实例信息写入到ServiceManager里面的内存注册表。

在这里插入图片描述

@RestController
@RequestMapping(UtilsAndCommons.NACOS_NAMING_CONTEXT + "/instance")
public class InstanceController {
	...
	
    @Autowired
    private ServiceManager serviceManager;

	...
	
	    @CanDistro
    @PostMapping
    @Secured(parser = NamingResourceParser.class, action = ActionTypes.WRITE)
    public String register(HttpServletRequest request) throws Exception {

        ...

        //请求发送的是Instance信息,接收请求的时解析出来的也是Instance
        final Instance instance = parseInstance(request);

        //保存客户端注册信息
        serviceManager.registerInstance(namespaceId, serviceName, instance);
        return "ok";
    }
	
	...
}

内存注册表

Nacos的ServiceManager保存了一个内存注册表,是一个双层的ConcurrentHashMap,外层的key是namespace(nacos的命名空间),内层的key是group拼上serviceName(服务名),value则是一个Service对象。

在这里插入图片描述

    /**
     * Map(namespace, Map(group::serviceName, Service)).
     */
    private final Map<String, Map<String, Service>> serviceMap = new ConcurrentHashMap<>();

Service里面并不是直接存放注册上来的服务实例信息,而是一个Map<String, Cluster>类型的clusterMap,Cluster代表集群,nacos这里把不同集群下的实例分组,把同一集群下的实例分到同一个Cluster里面,这样我们就可以就近选取同一集群下的实例。

在这里插入图片描述

Service#clusterMap

    //Service里面保存的又是一个Map,集群名称和Cluster实例的映射,Cluster里面才保存真正的服务实例
    private Map<String, Cluster> clusterMap = new HashMap<>();

Cluster里面才是真正存放注册上来的服务实例信息,有两个Set集合:

  • Set<Instance> persistentInstances: 持久化实例信息集合
  • Set<Instance> ephemeralInstances:非持久化实例信息集合

在这里插入图片描述

    @JsonIgnore
    private Set<Instance> persistentInstances = new HashSet<>(); //持久实例集合

    @JsonIgnore
    private Set<Instance> ephemeralInstances = new HashSet<>(); //临时实例集合

Instance里面存储的就是服务实例信息,什么ip地址端口号之类的。

内存注册表的更新

Eureka通过三级缓存解决了读写并发冲突,而Nacos则是通过写时复制(copy on write)解决读写并发冲突。

当有服务实例注册上来时,nacos会检查ServiceManger中的内存注册表是否有对应服务的Service对象,如果没有会创建一个空的Service对象放入到内存注册表,如果已经有对应服务的Service对象,则不需要创建。nacos不会马上去修改这个Service对象里面的服务实例信息,而是把该Service对象里面的所有Instance对象,连同新注册上来的Instance对象,收集到一个List中

在这里插入图片描述

这里只会收集所有临时实例,或者所有非临时实例,不会临时和非临时都全部收集上来。然后根据一定的规则生成一个key,把该key与前面的List存入一个内存的临时map中。然后把这个生成的key丢到一个tasks队列中。

在这里插入图片描述

后台会有一个线程轮询这个tasks队列,从队列中取得key,然后根据key从map中取出List,然后把List中的Instance按所属cluster进行分组。分好组后再逐一处理每一组中的Instance,同一组的Instance都是统一集群下的服务实例,再经过一定的处理后,最后会赋值到Cluster对象里面的两个Instance类型的Set集合中的其中一个(临时实例会赋值到ephemeralInstances,非临时实例赋值到persistentInstances),完成写时复制操作。

在这里插入图片描述

值得注意的是,这里nacos使用的了异步写内存注册表,性能是比Eureka要高的。

通知客户端服务变更、服务同步、健康检查

通知客户端服务变更这里与Eureka不一样,Eureka是开启了一个定时任务进行增量更新来感知注册中心服务端的变化。而nacos则是当有服务注册上来,会发送一个UDP请求,通知客户端有服务变更。

在这里插入图片描述

服务同步这nacos与Eureka做法差不多,也是采用了异步同步,不过nacos做了优化,它会攒够一批再一次性同步,也就是做了批量同步的优化。但是超过一定的期限,还是没有攒够一个批次的大小,还是会进行同步。

在这里插入图片描述

健康检查的定时任务是在创建Service的时候启动的,会定时对该Service里面的每个Instance实例进行检查,检查他们的客户端最后一次发送心跳的时间是否超过15s,如果是,则标记为不健康,如果超过30s,则进行服务下线(从内存注册表中剔除,并通知客户端)。

在这里插入图片描述

心跳也是由NamingService开启定时任务定时发送的,这个会在发送服务注册的http请求之前就会开启。

在这里插入图片描述

2.x版本nacos的变化

在nacos2.x以上的版本,如果是临时实例的注册,会走GRPC协议,如果是非临时实例的注册,会走http协议。
在这里插入图片描述
内存注册表的结构也发生了变化,那么与之对应的内存注册表读写的流程(也就是服务注册与服务发现的流程)也发生了变化。

ServiceManager里面不再直接存放双层内存注册表,而是放了一个Service自己与自己对于的map:ConcurrentHashMap<Service, Service> singletonRepository。

在这里插入图片描述

然后每一个连接上来的客户端,都会为它创建一个Client对象,有一个ClientManager管理这些Client对象。

在这里插入图片描述

然后Client里面有一个ConcurrentHashMap<Service, InstancePublishInfo>的map,key是注册上来的服务实例所属的Service,而value则是服务实例信息。

在这里插入图片描述
还有一个ClientServiceIndexesManager存放Service与所有对应的clientId之间的映射。
在这里插入图片描述

因为一个Service可能会有多个服务实例,每个服务实例注册上来时,都会创建一个Client,那么这里记录一个Service对应的有哪些服务实例,Service就可以拿到对应的clientId集合,进而拿到所有的Client对象,而Client对象中有存放了对应的服务实例信息,这样就可以拿到对应的服务实例信息。

于是服务发现的流程就是这样:

在这里插入图片描述

除了内存注册表以及服务注册与发现的变化外,服务同步、通知客户端服务变更、以及健康检查也发生了变化。

服务同步和通知客户端服务变更这两个操作也是异步的,这一点和1.x版本一样。由于2.x的nacos已经换成了GRPC协议,所以这里也是通过发送GRPC协议的请求进行处理,因为GRPC协议底层是长连接的,因此这里不需要再重复建立请求,只需要拿到对应的Connection连接对象,发送一个GRPC请求就可以。

在这里插入图片描述
1.x版本的nacos通过定时任务定时检测服务实例的最后心跳时间,如果超期了就进行服务下线。而2.x版本的nacos由于换成了GRPC协议的长连接,服务端和客户端之间没有再次建立请求的额外开销,因此增加了一个服务端主动探活的操作,如果客户端有响应,那么还是不会进行服务下线的。

在这里插入图片描述

2.x版本的nacos还对异步处理进行了封装,把事件监听机制封装到NotifyCenter中,把一些定时任务、延时任务、异步任务等的处理封装到了NacosTaskExecuteEngine中,这里就不细说了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1953324.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

西电网络空间安全综合953考研分享||西安电子科技大学

一、院校选择 如何选择适合自己的学校以及专业 1. 首先要对自己选择的学校有热情&#xff0c;选择自己最想去的学校 2. 其次选择在自己能力范围内努力能考上的学校&#xff0c;综合考虑地区&#xff08;不同地区公共课分数有一定的差别&#xff09;、学校&#xff08;建议跨…

MySQL索引、事务(数据库管理与高可用)

一、索引的概念 索引&#xff1a;排序的列表&#xff0c;对数据进行快速的查询&#xff1b; 针对不同的产品需求&#xff0c;或者不同的数据库结构&#xff0c;会创建不同的索引&#xff1b; 1&#xff1a;普通索引&#xff08;默认索引&#xff09; 2&#xff1a;唯一索引…

如何有效的进行小程序的优化

如今小程序已经成为了许多开发者开展业务&#xff0c;提供服务的重要平台 。所以如何有效的优化小程序成为了开发者关注的首要问题&#xff0c;以下是一份详细的小程序优化方案&#xff1a; 一、目标设定 明确小程序优化的主要目标&#xff0c;例如提高用户留存率、增加用户活…

writing classes ... [xxx of xxxx] 执行时间太长

一、问题展示 二、解决方法 打开设置【File - Settings…】修改堆大小

MySQL内如何改变编码格式

查找数据库的编码格式&#xff1a; show variables like character%;具体内容时这些 在创建表时设定编码格式&#xff1a; create database <要创建的数据库的名字> charset utf8; 修改数据库默认编码&#xff1a; set character_set_databaseutf8mb4; character_…

『 Linux 』信号概念与信号的产生 ( 万字 )

文章目录 信号概念前台进程与后台进程信号的本质硬件理解信号的产生Core dump 标志 信号概念 "信号"一词指用来传达信息或只是的各种形式的提示或标志; 在生活中常见的信号例如红绿灯,交通标志,短信通知等 在操作系统中,"信号"是一种用于异步通知进程发生特…

Flink大状态作业调优——DataStream篇

一、Flink 状态&#xff08;State&#xff09;简介 在流式计算中有些操作一次处理一个独立的事件(比如解析一个事件), 有些操作却需要记住多个事件的信息(比如窗口操作)。那些需要记住多个事件信息的操作就是有状态的。流式计算分为无状态计算和有状态计算两种情况。状态可以理…

jionlp根据词典进行行政区划补全

背景 需要对地址数据进行行政区划补全的,可以用下面的方法,当然是有条件限制的,只限于提供本省的词典和补全本身的地址数据,否则容易错乱 效果测试 lp = LocationParser() loc = 侨英街道乐海南里170号 res = lp(loc) print(res)1、安装或者更新 python安装 pip insta…

【秋招笔试题】小明的美食

解析&#xff1a;思维题。由于需要互不相同&#xff0c;每次操作取重复的值与最大值相加即可&#xff0c;这样即可保证相加后不会新增重复的值。因此统计重复值即可。 #include <iostream> #include <algorithm>using namespace std; const int maxn 1e5 5; int…

【高中数学/对数函数,指数函数】设方程10^x=|lg(-x)|的两根分别为x1,x1,则以下四选项正确的是? (PS:牛顿中值法失效的案例)

【问题】 设方程10^x|lg(-x)|的两根分别为x1,x1,则以下四选项正确的是&#xff1f; A.x1*x2<0 B.x1*x20 C.x1*x2>1 D.0<x1*x2<1 【解答】 10^x|lg(-x)|的两根&#xff0c;即函数y10^x与y|lg(-x)|的两个交点。 函数y10^x的曲线无须赘述&#xff0c;y|lg(-x)…

C++初阶学习第四弹——类与对象(中)

目录 一. 类的默认成员函数 二.六种默认成员函数 1、构造函数 1.1 构造函数的作用 1.2 特性 1.3 默认构造函数 2、析构函数 2.1 析构函数的作用 2.2 析构函数的用法 3、拷贝构造函数 3.1 拷贝构造函数的作用 3.2 特征 3.3 默认拷贝构造函数 三.总结 类与对象&…

LwIP入门实战 — 1 计算机网络简述

目录 1 计算机网络类别 2 常用网络协议与协议栈 2.1 常用网络协议 2.2 常用TCP/IP协议栈 3 网络协议的分层模型 4 协议层报文间的封装与拆封 5 WAN接口和LAN接口 1 计算机网络类别 广域网WAN(Wide Area Network)&#xff1a;广域网的作用范围通常为几十到几千公里&…

浅析JWT原理及牛客出现过的相关面试题

原文链接&#xff1a;https://kixuan.github.io/posts/f568/ 对jwt总是一知半解&#xff0c;而且项目打算写个关于JWT登录的点&#xff0c;所以总结关于JWT的知识及网上面试考察过的点 参考资料&#xff1a; Cookie、Session、Token、JWT_通俗地讲就是验证当前用户的身份,证明-…

记录|使用HslCommunication库进行写入Real数据的坑

项目场景&#xff1a; 现在已经通过HslCommunication连接上了PLC&#xff0c;需要对DB1.DBD10的位置处进行数据写入。 问题描述 但是进行将12.2写入指定位置DB1.DBD10时&#xff0c;发现无法从博图中实时检测到数据的写入。 下面是我当时错误的数据写入方法&#xff1a;【主…

Maven实战(一)- Maven安装与配置

Maven实战&#xff08;一&#xff09;- Maven安装与配置 文章目录 Maven实战&#xff08;一&#xff09;- Maven安装与配置1.下载安装包2.配置环境变量。3.安装目录分析4.设置HTTP代理5.镜像 前言&#xff1a; ​ 最近博主看完了《Maven实战》&#xff08;许晓斌著&#xff09;…

iOS collectionView 滑动出现空白

iOS collectionView 滑动出现空白 一个很常见的 banner 轮播&#xff0c;滑动的时候&#xff0c;有时候会出现空白&#xff0c;检查了下&#xff0c;发现代码没什么问题&#xff0c;上网查了也没啥结果&#xff0c;最后的解决方法是自定义layout解决 interface TMLoopViewLayo…

从0到1,AI我来了- (3)AI图片识别的理论知识-I

从上篇文章&#xff0c;我们分析通过Pytorch 封装的各种方法&#xff0c;解读了一遍图片识别训练到测试的流程。 这篇我们从理论上&#xff0c;理解下&#xff0c;图片是如何被识别的&#xff1f; 核心需要理解的内容有&#xff1f; 一张图片&#xff0c;如何被计算机读懂&a…

探索 Python 异步编程的利器:gevent 库

探索 Python 异步编程的利器&#xff1a;gevent 库 第一部分&#xff1a;背景介绍 在现代的软件开发中&#xff0c;异步编程模式因其在处理 I/O 密集型任务时的高效率而越来越受到重视。Python&#xff0c;作为一种动态、解释型的高级编程语言&#xff0c;其原生的异步编程支持…

如何判断IP是否属于网段10.134.208.0/20

首先想要判断IP是否属于网段&#xff0c;我们首先需要了解IP地址的组成结构&#xff1a; 网络IP地址的划分主要包括网络部分&#xff08;网络地址&#xff09;和主机部分&#xff0c;以及一个特殊的地址——广播地址。以下是详细的划分说明&#xff1a; 一、IP地址的组成 每…

中国式浪漫的源头之一:《楚辞》

文章目录 引言亦余心之所善兮,虽九死其犹未悔。惟草木之零落兮,恐美人之迟暮。沧浪之水清兮,可以濯吾缨。悲莫悲兮生别离,乐莫乐兮新相知。苟余心之端直兮,虽僻远其何伤。孰无施而有报兮,孰不实而有获?尺有所短,寸有所长。引言 楚辞中表里俱佳的文字,很有启发性。楚辞…