SpringCloud Alibaba 深入源码 - Nacos 分级存储模型、支撑百万服务注册压力、解决并发读写问题(CopyOnWrite)
目录
一、SpringCloudAlibaba 源码分析
1.1、SpringCloud & SpringCloudAlibaba 常用组件
1.2、Nacos的服务注册表结构是怎样的?
1.2.1、Nacos的分级存储模型(理论层)
1.2.2、Nacos 源码启动(准备工作)
1.2.3、运行 Nacos
1.2.4、Nacos的分级存储模型(源码层)
1.3、Nacos如何支撑数十万服务注册压力?
1.4、Nacos如何解决实例列表并发读写冲突问题
1.4.1、并发读写冲突
1.4.2、并发写冲突
一、SpringCloudAlibaba 源码分析
1.1、SpringCloud & SpringCloudAlibaba 常用组件
我们脑海中因该出现一幅图:
- 首先,我们肯定有无数个小的微服务.
- 这无数个微服务之间是不是要进行一个相互调用,那么就会用到 OpenFeign 这样的组件.
- 这么多服务要相互调用,怎么去管理呢,这就需要用到 nacos 组件去做注册中心,那么所有的服务就会去找注册中心去注册自己的服务.
- 那么我拉取到的服务可能是一个列表,那么将来在远程调用的时候就需要做负载均衡,就需要使用 LoadBalancer 这个组件.
- 这么多服务将来要做统一配置的管理怎么办,就需要引入 nacos 作为配置中心.
- 这时候微服务集群就形成了,将来对外提供服务,是不是随便什么人都能访问呢?显然不行,所有在微服务群前面就需要有 gateway 网关 作为入口.
- 那么就算你可以访问了,万一流量激增,引起微服务雪崩,给我整个服务搞崩了,肯定不行,因此就需要 sentinel 来做限流、熔断降级保护.
- 还有一个问题,在分布式系统下,就会引发分布式事务问题,如何解决呢,这就需要 Seata 上场了.
实际上,微服务的组件远不止于此,还有很多的组件,但是以上呢,就是我们最常用的几个组件啦~
1.2、Nacos的服务注册表结构是怎样的?
Tips:要了解Nacos的服务注册表结构,需要从以下两方面入手
1. Nacos的分级存储模型 2.Nacos的服务端源码分析
1.2.1、Nacos的分级存储模型(理论层)
a)Nacos 分级模型中最外层就是 namespace,起到一个环境隔离的作用,比如我们开发的时候会去区分开发环境、测试环境、生产环境...等等.
b)现在环境隔离好了,比如开发环境下,我们肯定是有很多很多的服务,这时候我们就可以业务模块进行分组,比如交易模块(分一个组),里面就会有像 订单、支付有关的微服务. (像阿里这种服成千上万个服务,进行分组管理就会很方便,但是小型企业的就没必要了,一般就使用默认组即可).
c)那么分组下面就到了一个个微服务了,而服务只是一个概念,提供了这样一个功能,将来为了保证服务的一个高可用,肯定就需要把每个服务部署成集群,而且部署的时候不能只是简单的说就整两太那么简单,肯定要部署到全国各地不同的机房,保证了异地容灾,不至于一个机房毁了,整个服务崩溃.
d)集群的下面就是才是我们具体的实例,可想而知,一个集群下肯定也是有多个实例的.
问题来了:这么一个分级存储的模型怎么用 java 代码来实现的呢?如果让你来实现,会用什么呢?我们是不是可以用 map 的 key value 结构去存储,接下来我们就来看看具体的源码怎么实现~
1.2.2、Nacos 源码启动(准备工作)
a)想要进行到 Nacos 源码层面进行分析,首先需要我们去官网下载好 Nacos 源码:https://github.com/alibaba/nacos
这里以 1.4.2 的 Nacos 版本为例
b)将 nacos 源码导入到工程当中,将其修改为模块
c)Nacos底层的数据通信会基于 protobuf 对数据做序列化和反序列化。并将对应的 proto 文件定义在了consistency这个子模块中:
d)安装 protoc:https://github.com/protocolbuffers/protobuf/releases
配置环境变量.
e)进入 nacos-1.4.2 的 consistency 模块下的src/main目录下,输入以下两个命令进行编译,生成对应的 Java 文件.
protoc --java_out=./java ./proto/consistency.proto
protoc --java_out=./java ./proto/Data.proto
entity 下就可以看到生成了如下代码:
1.2.3、运行 Nacos
a)添加 Nacos 的 SpringBoot 服务,指定启动模式为单机
b)运行的时候如果提到 Java 发行版本的问题,记得去改一下 JDK 版本
1.2.4、Nacos的分级存储模型(源码层)
a)Nacos 的分级存储模型对应到源码中,实际上就是一个多层嵌套 Map,key 就是 String 类型的 namespace,而他的 value 又是一个 Map.
b)这个第二层的 Map 就表示 group 和 服务了,key 就服务名称,而 value 就是一个服务 service.
c)服务实际上就是一个类,由于一个服务往往是有多个集群的,因此在 service 类中又维护了一个 map,key 就是集群名称(例如,上海、广州、杭州...).
d)他的值 cluster 集群也是一个类,这个类里面就维护了两个 Set 集合,一个是临时实例,另一个就是非临时实例.
1.3、Nacos如何支撑数十万服务注册压力?
a)首先 nacos 肯定是要做成一个集群的,那么就可以对服务注册请求左负载均衡,会大大减轻压力.
b)其次,nacos 内部接收到服务注册请求时,不会立即更新到注册表中,而是将服务注册的任务放到了一个阻塞队列中,然后就响应给客户端了,但是实际上在这个注册的动作还是没有完成的,而是后续开启一个线程池,写了一个死循环,获取阻塞队列中的队头元素(如果存在就获取,不存在就阻塞等待,直到阻塞队列中有新元素为止). 因此这个更新动作实际上是异步实现的.
如下源码:
c)无论是更新本地列表,还是集群的一致性操作,都是通过异步执行的. 当然这些都是临时实例啊,非临时实例的话就不一样了,因为要保证强一致性,因此他的性能就难以保障了,所以在默认请情况下,所有实例都是临时的,性能会更好一点.
1.4、Nacos如何解决实例列表并发读写冲突问题
1.4.1、并发读写冲突
a)首先,Nacos 的实例列表,实际上也就是 Map,这个集合里面装的就是旧的列表,现在要对这个旧的列表做修改,那一边写一边读可能会造成脏读的问题.
b)因此,处理这种问题,我们最直接的可能就是想到使用加锁来处理,但是加锁的开销也不小,涉及到用户态到内核态的转换... 那么 Nacos 这里采取的是 CopyOnWrite 技术,也就是说,他不是直接来改这个集合中的数据,而是先把这个集合中的数据拷贝了一份,放到一个全新的集合中,然后再在这个全新的集合中进行更新修改操作,改完了之后再直接覆盖掉旧数据.
c)而这个过程中读取的是旧的实例列表,因此不会受到任何影响.
如下源码:
public List<Instance> updateIpAddresses(Service service, String action, boolean ephemeral, Instance... ips)throws NacosException {// 根据namespaceId、serviceName获取当前服务的实例列表,返回值是Datum// 第一次来,肯定是nullDatum datum = consistencyService.get(KeyBuilder.buildInstanceListKey(service.getNamespaceId(), service.getName(), ephemeral));// 得到服务中现有的实例列表List<Instance> currentIPs = service.allIPs(ephemeral);// 创建map,保存实例列表,key为ip地址,value是Instance对象Map<String, Instance> currentInstances = new HashMap<>(currentIPs.size());// 创建Set集合,保存实例的instanceIdSet<String> currentInstanceIds = Sets.newHashSet();// 遍历要现有的实例列表for (Instance instance : currentIPs) {// 添加到map中currentInstances.put(instance.toIpAddr(), instance);// 添加instanceId到set中currentInstanceIds.add(instance.getInstanceId());}// 创建map,用来保存更新后的实例列表Map<String, Instance> instanceMap;if (datum != null && null != datum.value) {// 如果服务中已经有旧的数据,则先保存旧的实例列表instanceMap = setValid(((Instances) datum.value).getInstanceList(), currentInstances);} else {// 如果没有旧数据,则直接创建新的mapinstanceMap = new HashMap<>(ips.length);}// 遍历实例列表for (Instance instance : ips) {// 判断服务中是否包含要注册的实例的cluster信息if (!service.getClusterMap().containsKey(instance.getClusterName())) {// 如果不包含,创建新的clusterCluster cluster = new Cluster(instance.getClusterName(), service);cluster.init();// 将集群放入service的注册表service.getClusterMap().put(instance.getClusterName(), cluster);Loggers.SRV_LOG.warn("cluster: {} not found, ip: {}, will create new cluster with default configuration.",instance.getClusterName(), instance.toJson());}// 删除实例 or 新增实例 ?if (UtilsAndCommons.UPDATE_INSTANCE_ACTION_REMOVE.equals(action)) {instanceMap.remove(instance.getDatumKey());} else {// 新增实例,instance生成全新的instanceIdInstance oldInstance = instanceMap.get(instance.getDatumKey());if (oldInstance != null) {instance.setInstanceId(oldInstance.getInstanceId());} else {instance.setInstanceId(instance.generateInstanceId(currentInstanceIds));}// 放入instance列表instanceMap.put(instance.getDatumKey(), instance);}}if (instanceMap.size() <= 0 && UtilsAndCommons.UPDATE_INSTANCE_ACTION_ADD.equals(action)) {throw new IllegalArgumentException("ip list can not be empty, service: " + service.getName() + ", ip list: " + JacksonUtils.toJson(instanceMap.values()));}// 将instanceMap中的所有实例转为List返回return new ArrayList<>(instanceMap.values());
}
1.4.2、并发写冲突
a)对于并发写冲突,也就是说同时有多个线程来拷贝我们的实例,即使通过 CopyOnWrite 技术,你拷贝一份,我也拷贝一份,然后大家都各写各的,最后都去覆盖同一个旧的列表,这个时候还是会出现写冲突的问题.
b)因此,这里代码中的处理实际上就是直接给服务加锁,因此访问同一个服务的多个实例就只能串行执行了.
如下源码:
相关文章:

SpringCloud Alibaba 深入源码 - Nacos 分级存储模型、支撑百万服务注册压力、解决并发读写问题(CopyOnWrite)
目录 一、SpringCloudAlibaba 源码分析 1.1、SpringCloud & SpringCloudAlibaba 常用组件 1.2、Nacos的服务注册表结构是怎样的? 1.2.1、Nacos的分级存储模型(理论层) 1.2.2、Nacos 源码启动(准备工作) 1.2.…...

算法训练营Day45
#Java #动态规划 Feeling and experiences: 最长公共子序列:力扣题目链接 给定两个字符串 text1 和 text2,返回这两个字符串的最长 公共子序列 的长度。如果不存在 公共子序列 ,返回 0 。 一个字符串的 子序列 是指这样一个新…...

【Redis漏洞利用总结】
前言 redis是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。Redis默认使用 6379 端口。 一、redis未授权访问漏洞 0x01 漏洞描述 描述: Redis是一套开源的使用ANSI C编写、支持网络、可基于内存…...

SPI 动态服务发现机制
SPI(Service Provier Interface)是一种服务发现机制,通过ClassPath下的META—INF/services文件查找文件,自动加载文件中定义的类,再调用forName加载; spi可以很灵活的让接口和实现分离, 让API提…...

【C++进阶07】哈希表and哈希桶
一、哈希概念 顺序结构以及平衡树中 元素关键码与存储位置没有对应关系 因此查找一个元素 必须经过关键码的多次比较 顺序查找时间复杂度为O(N) 平衡树中为树的高度,即O( l o g 2 N log_2 N log2N) 搜索效率 搜索过程中元素的比较次数 理想的搜索方法:…...

Go 语言实现冒泡排序算法的简单示例
以下是使用 Go 语言实现冒泡排序算法的简单示例: package mainimport "fmt"func bubbleSort(arr []int) {n : len(arr)for i : 0; i < n-1; i {for j : 0; j < n-i-1; j {if arr[j] > arr[j1] {// 交换元素arr[j], arr[j1] arr[j1], arr[j]}}}…...

JAVA 学习 面试(五)IO篇
BIO是阻塞I/O,NIO是非阻塞I/O,AIO是异步I/O。BIO每个连接对应一个线程,NIO多个连接共享少量线程,AIO允许应用程序异步地处理多个操作。NIO,通过Selector,只需要一个线程便可以管理多个客户端连接࿰…...

vue3相比vue2的效率提升
1、静态提升 2、预字符串化 3、缓存事件处理函数 4、Block Tree 5、PatchFlag 一、静态提升 在vue3中的app.vue文件如下: 在服务器中,template中的内容会变异成render渲染函数。 最终编译后的文件: 1.静态节点优化 那么这里为什么是两部分…...

web terminal - 如何在mac os上运行gotty
gotty可以让你使用web terminal的方式与环境进行交互,实现终端效果 假设你已经配置好了go环境,首先使用go get github.com/yudai/gotty命令获取可执行文件,默认会安装在$GOPATH/bin这个目录下,注意如果你的go版本比较高ÿ…...

机械设计-哈工大课程学习-螺纹连接
圆柱螺纹主要几何参数螺纹参数 ①外径(大径),与外螺纹牙顶或内螺纹牙底相重合的假想圆柱体直径。螺纹的公称直径即大径。 ②内径(小径),与外螺纹牙底或内螺纹牙顶相重合的假想圆柱体直径。 ③中径ÿ…...

ai绘画|stable diffusion的发展史!简短易懂!!!
手把手教你入门绘图超强的AI绘画,用户只需要输入一段图片的文字描述,即可生成精美的绘画。给大家带来了全新保姆级教程资料包 (文末可获取) 一、stable diffusion的发展史 本文目标:学习交流 对于熟悉SD的同学&#x…...

水塘抽样算法
水塘抽样算法 1、问题描述 最近经常能看到面经中出现在大数据流中的随机抽样问题 即:当内存无法加载全部数据时,如何从包含未知大小的数据流中随机选取k个数据,并且要保证每个数据被抽取到的概率相等。 假设数据流含有N个数,我…...

easyui渲染隐藏域<input type=“hidden“ />为textbox可作为分割条使用
最近在修改前端代码的时候,偶然发现使用javascript代码渲染的方式将<input type"hidden" />渲染为textbox时,会显示一个神奇的效果,这个textbox输入框并不会隐藏,而是显示未一个细条,博主发现非常适合…...

100天精通Python(实用脚本篇)——第113天:基于Tesseract-OCR实现OCR图片文字识别实战
文章目录 专栏导读1. OCR技术介绍2. 模块介绍3. 模块安装4. 代码实战4.1 英文图片测试4.2 数字图片测试4.3 中文图片识别 书籍分享 专栏导读 🔥🔥本文已收录于《100天精通Python从入门到就业》:本专栏专门针对零基础和需要进阶提升的同学所准…...

Go七天实现RPC
0.前言 本文是学习自7天用Go从零实现RPC框架GeeRPC | 极客兔兔 在此基础上,加入自己的学习过程与理解。 1.RPC 框架 RPC(Remote Procedure Call,远程过程调用)是一种计算机通信协议,允许调用不同进程空间的程序。RPC 的客户端和服务器可以…...

Elasticsearch:和 LIamaIndex 的集成
LlamaIndex 是一个数据框架,供 LLM 应用程序摄取、构建和访问私有或特定领域的数据。 LlamaIndex 是开源的,可用于构建各种应用程序。 在 GitHub 上查看该项目。 安装 在 Docker 上设置 Elasticsearch 使用以下 docker 命令启动单节点 Elasticsearch 实…...

QT基础篇(14)QT操作office实例
1.QT操作office的基本方式 通过QT操作Office软件,可以使用Qt的QAxObject类来进行操作。下面是一个例子,展示了通过Qt操作Excel的基本方式: #include <QApplication> #include <QAxObject>int main(int argc, char *argv[]) {QA…...

重拾计网-第四弹 计算机网络性能指标
ps:本文章的图片内容来源都是来自于湖科大教书匠的视频,声明:仅供自己复习,里面加上了自己的理解 这里附上视频链接地址:1.5 计算机网络的性能指标(1)_哔哩哔哩_bilibili 目录 &#x…...

【Vue】Vue 路由的配置及使用
目录捏 前言一、路由是什么?1.前端路由2.后端路由 二、路由配置1.安装路由2.配置路由 三、路由使用1.route 与 router2. 声明式导航3. 指定组件的呈现位置 四、嵌套路由(多级路由)五、路由重定向1.什么是路由重定向?2.设置 redire…...

网络安全事件分级指南
一、特别重大网络安全事件 符合下列情形之一的,为特别重大网络安全事件: 1.重要网络和信息系统遭受特别严重的系统损失,造成系统大面积瘫痪,丧失业务处理能力。 2.国家秘密信息、重要敏感信息、重要数据丢失或被窃取、篡改、假…...

uniapp组件库SwipeAction 滑动操作 使用方法
目录 #平台差异说明 #基本使用 #修改按钮样式 #点击事件 #API #Props #Event 该组件一般用于左滑唤出操作菜单的场景,用的最多的是左滑删除操作。 注意 如果把该组件通过v-for用于左滑删除的列表,请保证循环的:key是一个唯一值,可以…...

YARN节点故障的容错方案
YARN节点故障的容错方案 1. RM高可用1.1 选主和HA切换逻辑 2. NM高可用2.1 感知NM节点异常2.2 异常NM上的任务处理 4. 疑问和思考4,1 RM感知NM异常需要10min,对于app来说是否太长了? 5. 参考文档 本文主要探讨yarn集群的高可用容错方案和容错能力的探讨。…...

C++后端笔记
C后端笔记 资源整理一、高级语言程序设计1.1 进制1.2 程序结构基本知识1.3 数据类型ASCII码命名规则变量间的赋值浮点型变量的作用字符变量常变量 const运算符 二、高级语言程序设计(荣) 资源整理 C后端开发学习路线及推荐学习时间 C基础知识大全 C那…...

JavaEE中什么是Web容器?
Web容器(也称为Servlet引擎)是一个用于执行Java Servlet和JSP的服务器端环境。它负责管理和执行在其上运行的Web应用程序。 Tomcat是Web容器 Apache Tomcat 是一个流行的开源的Web容器,它实现了Java Servlet和JavaServer Pages(…...

MySQL 8.0 架构 之错误日志文件(Error Log)(1)
文章目录 MySQL 8.0 架构 之错误日志文件(Error Log)(1)MySQL错误日志文件(Error Log)MySQL错误日志在哪里Window环境Linux环境 错误日志相关参数log_error_services 参考 【声明】文章仅供学习交流&#x…...

51单片机实验课一
实验任务一:实现控制8个发光管的亮(灭) #include <REGX52.H> void Delay1ms(unsigned int xms) //11.0592MHz {unsigned char i, j;while(xms){xms--;i 12;j 169;do{while (--j);} while (--i);} } void main() {while(1){P20;//八…...

【.NET Core】多线程之线程池(ThreadPool)详解(一)
【.NET Core】多线程之线程池(ThreadPool)详解(一) 文章目录 【.NET Core】多线程之线程池(ThreadPool)详解(一)一、概述二、线程池的应用范围三、线程池特性3.1 线程池线程中的异常…...

圆的参数方程是如何推导的?
圆的参数方程是如何推导的? 1. 圆的三种参数表示2. 三角函数万能公式3. 回到圆的参数方程1. 圆的三种参数表示 已知圆的第一种参数方程为: x 2 + y 2 = r x^2+y^2=r x2+y2=r 圆的图像如下: 通过上图,不难理解,圆的参数方程还可以用三角函数表示,也就是第二种参数表…...

sqlmap使用教程(2)-连接目标
目录 连接目标 1.1 设置认证信息 1.2 配置代理 1.3 Tor匿名网络 1.4 检测WAF/IPS 1.5 调整连接选项 1.6 处理连接错误 连接目标 场景1:通过代理网络上网,需要进行相应配置才可以成功访问目标主机 场景2:目标网站需要进行身份认证后才…...

c++ http第一个服务
c http第一个服务 一、下载相关依赖:这是一个git开源项目 代码仓地址 二、演示代码,编译参数:g test.cpp -I/**** -lpthread #include <httplib.h> using namespace httplib;void wuhan(const Request &req, Response &res) …...