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.国家秘密信息、重要敏感信息、重要数据丢失或被窃取、篡改、假…...
浅谈 React Hooks
React Hooks 是 React 16.8 引入的一组 API,用于在函数组件中使用 state 和其他 React 特性(例如生命周期方法、context 等)。Hooks 通过简洁的函数接口,解决了状态与 UI 的高度解耦,通过函数式编程范式实现更灵活 Rea…...
SkyWalking 10.2.0 SWCK 配置过程
SkyWalking 10.2.0 & SWCK 配置过程 skywalking oap-server & ui 使用Docker安装在K8S集群以外,K8S集群中的微服务使用initContainer按命名空间将skywalking-java-agent注入到业务容器中。 SWCK有整套的解决方案,全安装在K8S群集中。 具体可参…...
通过Wrangler CLI在worker中创建数据库和表
官方使用文档:Getting started Cloudflare D1 docs 创建数据库 在命令行中执行完成之后,会在本地和远程创建数据库: npx wranglerlatest d1 create prod-d1-tutorial 在cf中就可以看到数据库: 现在,您的Cloudfla…...
基于当前项目通过npm包形式暴露公共组件
1.package.sjon文件配置 其中xh-flowable就是暴露出去的npm包名 2.创建tpyes文件夹,并新增内容 3.创建package文件夹...
在Ubuntu中设置开机自动运行(sudo)指令的指南
在Ubuntu系统中,有时需要在系统启动时自动执行某些命令,特别是需要 sudo权限的指令。为了实现这一功能,可以使用多种方法,包括编写Systemd服务、配置 rc.local文件或使用 cron任务计划。本文将详细介绍这些方法,并提供…...
高危文件识别的常用算法:原理、应用与企业场景
高危文件识别的常用算法:原理、应用与企业场景 高危文件识别旨在检测可能导致安全威胁的文件,如包含恶意代码、敏感数据或欺诈内容的文档,在企业协同办公环境中(如Teams、Google Workspace)尤为重要。结合大模型技术&…...
微服务商城-商品微服务
数据表 CREATE TABLE product (id bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 商品id,cateid smallint(6) UNSIGNED NOT NULL DEFAULT 0 COMMENT 类别Id,name varchar(100) NOT NULL DEFAULT COMMENT 商品名称,subtitle varchar(200) NOT NULL DEFAULT COMMENT 商…...
从零实现STL哈希容器:unordered_map/unordered_set封装详解
本篇文章是对C学习的STL哈希容器自主实现部分的学习分享 希望也能为你带来些帮助~ 那咱们废话不多说,直接开始吧! 一、源码结构分析 1. SGISTL30实现剖析 // hash_set核心结构 template <class Value, class HashFcn, ...> class hash_set {ty…...
Reasoning over Uncertain Text by Generative Large Language Models
https://ojs.aaai.org/index.php/AAAI/article/view/34674/36829https://ojs.aaai.org/index.php/AAAI/article/view/34674/36829 1. 概述 文本中的不确定性在许多语境中传达,从日常对话到特定领域的文档(例如医学文档)(Heritage 2013;Landmark、Gulbrandsen 和 Svenevei…...
【生成模型】视频生成论文调研
工作清单 上游应用方向:控制、速度、时长、高动态、多主体驱动 类型工作基础模型WAN / WAN-VACE / HunyuanVideo控制条件轨迹控制ATI~镜头控制ReCamMaster~多主体驱动Phantom~音频驱动Let Them Talk: Audio-Driven Multi-Person Conversational Video Generation速…...
