微服务框架 SpringCloud微服务架构 微服务面试篇 54 微服务篇 54.3 Nacos 如何支撑数十万服务注册压力?

news2024/11/24 8:09:17

微服务框架

【SpringCloud+RabbitMQ+Docker+Redis+搜索+分布式,系统详解springcloud微服务技术栈课程|黑马程序员Java微服务】

微服务面试篇

文章目录

      • 微服务框架
      • 微服务面试篇
      • 54 微服务篇
        • 54.3 Nacos 如何支撑数十万服务注册压力?
          • 54.3.1 Nacos 服务端源码

54 微服务篇

54.3 Nacos 如何支撑数十万服务注册压力?

54.3.1 Nacos 服务端源码

上次我们在 源码里面 看了Nacos 服务注册表的结构

在这里插入图片描述

下面来看看 它到底是怎么形成 这样一种结构,并且 把数据存进去 的?

回到请求入口

在这里插入图片描述

详细解读 一下

微服务 启动的时候就会向这个接口发起注册 请求POST

@CanDistro
@PostMapping
@Secured(parser = NamingResourceParser.class, action = ActionTypes.WRITE)
public String register(HttpServletRequest request) throws Exception {
    // 【从request 中获取namespaceId】
    final String namespaceId = WebUtils
            .optional(request, CommonParams.NAMESPACE_ID, Constants.DEFAULT_NAMESPACE_ID);
    // 【获取服务名称】
    final String serviceName = WebUtils.required(request, CommonParams.SERVICE_NAME);
    NamingUtils.checkServiceNameFormat(serviceName);
    
    // 把request 中的参数封装为Instance【实例】 对象
    final Instance instance = parseInstance(request);
    
    // 【注册实例】
    serviceManager.registerInstance(namespaceId, serviceName, instance);
    return "ok";
}

OK, 这个方法大致就算完了 ,跟进 registerInstance 方法

public void registerInstance(String namespaceId, String serviceName, Instance instance) throws NacosException {
    
    // 如果是 第一次,则创建空的服务
    createEmptyService(namespaceId, serviceName, instance.isEphemeral());
    
    // 从注册表里,拿到Service
    Service service = getService(namespaceId, serviceName);
    
    
    if (service == null) {
        throw new NacosException(NacosException.INVALID_PARAM,
                "service not found, namespace: " + namespaceId + ", service: " + serviceName);
    }
    
    // 添加实例到 Service 中
    addInstance(namespaceId, serviceName, instance.isEphemeral(), instance);
}

OK,简单看看初始化 的动作createEmptyService 这个方法

public void createEmptyService(String namespaceId, String serviceName, boolean local) throws NacosException {
    createServiceIfAbsent(namespaceId, serviceName, local, null);
}

OK, 又调了一个方法

其实就在下面

在这里插入图片描述

public void createServiceIfAbsent(String namespaceId, String serviceName, boolean local, Cluster cluster)
        throws NacosException {
    
    // 尝试从注册表中 获取服务
    Service service = getService(namespaceId, serviceName);
    if (service == null) {
        
        // 如果为空,则说明该服务是 第一次注册,则创建新的Service
        Loggers.SRV_LOG.info("creating empty service {}:{}", namespaceId, serviceName);
        service = new Service();
        service.setName(serviceName);
        service.setNamespaceId(namespaceId);
        service.setGroupName(NamingUtils.getGroupName(serviceName));
        // now validate the service. if failed, exception will be thrown
        // 记录服务最后一次 更新的时间【其实就是当前时间】
        service.setLastModifiedMillis(System.currentTimeMillis());
        service.recalculateChecksum();
        if (cluster != null) {
            cluster.setService(service);
            service.getClusterMap().put(cluster.getName(), cluster);
        }
        service.validate();
        
        //把服务放入 注册表
        putServiceAndInit(service);
        if (!local) {
            addOrReplaceService(service);
        }
    }
}

再仔细 看看putServiceAndInit 这个方法

private void putServiceAndInit(Service service) throws NacosException {

	//把Service 放入注册表
    putService(service);
    service = getService(service.getNamespaceId(), service.getName());
    //初始化【健康检测】
    service.init();
    
    // 服务状态变更的 监听
    consistencyService
            .listen(KeyBuilder.buildInstanceListKey(service.getNamespaceId(), service.getName(), true), service);
    consistencyService
            .listen(KeyBuilder.buildInstanceListKey(service.getNamespaceId(), service.getName(), false), service);
    Loggers.SRV_LOG.info("[NEW-SERVICE] {}", service.toJson());
}

到这儿 ,其实一个 空的服务,就完全初始化完毕 了【即 createEmptyService 方法就算完成了 】

在这里插入图片描述

接下看下去 ,就是热乎的服务从注册表 中拿出来

判空

最后添加实例 到Service 中

跟进最后一个addInstance 方法

在这里插入图片描述

public void addInstance(String namespaceId, String serviceName, boolean ephemeral, Instance... ips)
        throws NacosException {
    
    // 给当前服务生成 一个唯一标识【可以理解为 serviceId】
    String key = KeyBuilder.buildInstanceListKey(namespaceId, serviceName, ephemeral);
    
    // 从注册表 中拿到Service
    Service service = getService(namespaceId, serviceName);
    
    // service为锁对象,同一个服务的多个实例,只能串行 来完成注册【避免了并发的 修改】
    synchronized (service) {
        // 拷贝注册表中 旧的实例列表,然后结合新注册 的实例,得到最终的实例列表【COPY】
        List<Instance> instanceList = addIpAddresses(service, ephemeral, ips);
        
        // 封装 新实例列表到 instances 对象中
        Instances instances = new Instances();
        instances.setInstanceList(instanceList);
        
        //consistency 一致性。【更新注册表 (① 更新本地注册表 ② 同步给Nacos 集群中的其他节点)】【最耗时的步骤】
        consistencyService.put(key, instances);
    }
}

再跟进最后这个 put 方法

在这里插入图片描述

@Override
public void put(String key, Record value) throws NacosException {
    mapConsistencyService(key).put(key, value);
}

OK,再进这个 put 方法

在这里插入图片描述

@Override
public void put(String key, Record value) throws NacosException {
    
    // 更新本地注册表 【异步的】【后面的“死循环”】
    onPut(key, value);
    
    // “同步(bushi)”给Nacos 中的其他节点【同时 异步的将数据同步给Nacos 集群中的其他节点】
    distroProtocol.sync(new DistroKey(key, KeyBuilder.INSTANCE_LIST_KEY_PREFIX), DataOperation.CHANGE,
            globalConfig.getTaskDispatchPeriod() / 2);
}

继续跟进 onPut 方法

在这里插入图片描述

public void onPut(String key, Record value) {
    
    // 判断是否是 临时实例
    if (KeyBuilder.matchEphemeralInstanceListKey(key)) {
        
        // 封装实例列表 封装到Datum
        Datum<Instances> datum = new Datum<>();
        
        // value 是服务中的实例列表 Instances
        datum.value = (Instances) value;
        // key 是serviceId
        datum.key = key;
        datum.timestamp.incrementAndGet();
        
        // 以serviceId 为key,以 Datum为值,缓存起来
        dataStore.put(key, datum);
    }
    
    if (!listeners.containsKey(key)) {
        return;
    }
    
    // 把serviceId 和 当前的 操作类型存入task 列表notifier
    notifier.addTask(key, DataOperation.CHANGE);
}

再进到 addTask 这个方法

在这里插入图片描述

public void addTask(String datumKey, DataOperation action) {
    
    if (services.containsKey(datumKey) && action == DataOperation.CHANGE) {
        return;
    }
    if (action == DataOperation.CHANGE) {
        services.put(datumKey, StringUtils.EMPTY);
    }
    
    // 把serviceId 和事件【放入】 阻塞队列【“活儿接了,不忙做”】
    tasks.offer(Pair.with(datumKey, action));
}

回到上一个onPut 方法,

在这里插入图片描述

notifier 这个东西到底是个啥

看看当前这个 类 init()方法,往上滑

@PostConstruct
public void init() {
    // 利用线程池,执行 notifier
    GlobalExecutor.submitDistroNotifyTask(notifier);
}

在这里插入图片描述

这下直接 跟入 notifier 的run 方法中

在这里插入图片描述

厉害的是,居然是个 死循环,妙啊

@Override
public void run() {
    Loggers.DISTRO.info("distro notifier started");
    
    // 死循环
    for (; ; ) {
        try {
            // 从阻塞队列 找那个获取任务, 6666666
            Pair<String, DataOperation> pair = tasks.take();
            
            // 执行任务,更新服务列表
            handle(pair);
        } catch (Throwable e) {
            Loggers.DISTRO.error("[NACOS-DISTRO] Error while handling notifying task", e);
        }
    }
}

OK,到这里onPut方法 算完事儿了

在这里插入图片描述

继续下面 这个 同步方法,跟进去

在这里插入图片描述

在这里插入图片描述

public void sync(DistroKey distroKey, DataOperation action, long delay) {
    
    // 获取Nacos 中的所有成员,除了我自己
    for (Member each : memberManager.allMembersWithoutSelf()) {
        
        //【遍历】
        DistroKey distroKeyWithTarget = new DistroKey(distroKey.getResourceKey(), distroKey.getResourceType(),
                each.getAddress());
        DistroDelayTask distroDelayTask = new DistroDelayTask(distroKeyWithTarget, action, delay);
        distroTaskEngineHolder.getDelayTaskExecuteEngine().addTask(distroKeyWithTarget, distroDelayTask);
        if (Loggers.DISTRO.isDebugEnabled()) {
            Loggers.DISTRO.debug("[DISTRO-SCHEDULE] {} to {}", distroKey, each.getAddress());
        }
    }
}

OK,该回答问题了

问题说明:考察对Nacos源码的掌握情况

难易程度:难

参考话术

Nacos内部接收到注册的请求时,不会立即写数据,而是将服务注册的任务放入一个阻塞队列就立即响应给客户端。然后利用线程池读取阻塞队列中的任务,异步【异步是设计最精妙的】来完成实例更新,从而提高并发写能力。

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

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

相关文章

信息提取 Information Extraction

定义 信息提取任务是从一组非结构化自然语言文本中的每个文本中识别有关实体、关系或事件的预定义类的信息&#xff0c;并通过以下任一方式以结构化形式记录此信息&#xff1a; • 注释源文本&#xff0c;使用 XML 标签 • 填写与文本分开的数据结构&#xff0c;例如模板或数据…

centos8 nginx 开启 ssl(https)

centos8 nginx 开启 ssl&#xff08;https&#xff09;首先申请证书&#xff0c;以阿里云为例centos8 nginx 安装 sslnginx 配置文件遇到问题一直没有给域名开启 https&#xff0c;在 Cesium 沙盒模拟中使用 http 资源会提示错误&#xff0c;于是想着把 https 打开&#xff0c;…

【计算机毕业设计】基于HTML+CSS+JavaScript大学生心理咨询网设计毕业论文源码

&#x1f389;精彩专栏推荐 &#x1f4ad;文末获取联系 ✍️ 作者简介: 一个热爱把逻辑思维转变为代码的技术博主 &#x1f482; 作者主页: 【主页——&#x1f680;获取更多优质源码】 &#x1f393; web前端期末大作业&#xff1a; 【&#x1f4da;毕设项目精品实战案例 (10…

53.Python的f格式化字符串

53.f格式化字符串 文章目录53.f格式化字符串1.知识回顾2.f格式化字符串3.课堂练习4. 字符串格式化总结1.知识回顾 之前我们学了2种格式化方法&#xff1a; %占位符格式化 .format字符串格式化 【目标任务】 原样输出&#xff1a;大家好&#xff0c;我叫XXX&#xff0c;来自X…

2022卡塔尔世界杯 | 致我们每个人那份绿茵场上的足球梦

一个沾满草腥味的皮球&#xff0c;在经过一系列有名气的或者没名气的金脚触碰之后&#xff0c;缓慢地或者迅速地躲过后卫和门将大义凛然的身体&#xff0c;越过了那个由门柱和横梁构成的透明长方体平面…… 如果你不是球迷&#xff0c;你真的很难理解这么一个皮革制品的物理位移…

全网最详细的网络安全入门教程,一篇文章满足你

随着当下正在发生的互联网革命&#xff0c;以及乌俄局势的网络战&#xff0c;网络安全行业已经受到更多人的关注&#xff0c;而这个行业的人才缺口将继续呈指数型扩张。借用行业内某大咖的一句话“网络安全行业的人才成材率极低”&#xff0c;究其原因还是因为网络安全从业者所…

MD编辑器使用方法

这里写自定义目录标题欢迎使用Markdown编辑器新的改变功能快捷键合理的创建标题&#xff0c;有助于目录的生成如何改变文本的样式插入链接与图片如何插入一段漂亮的代码片生成一个适合你的列表创建一个表格设定内容居中、居左、居右SmartyPants创建一个自定义列表如何创建一个注…

如何解决 科班和自学编程出身差别,差距又在哪里?

自学和科班的差别最大的在于学习的人本身——不在呼是否科班。 自学编程和计算机科班出身的差别在哪&#xff1f; 不同的人持有不同的看法其实相辅相成才能发挥最大作用 A先生&#xff1a;听从父母的意见选择了编程专业&#xff0c;学习天天逃课&#xff0c;毕业后父母帮忙找…

十一、Docker 中redis集群扩容、缩容

接上篇redis集群3主3从搭建,如果您没看过,建议先看下:https://blog.csdn.net/u011837804/article/details/128321703 适逢双十一等大型活动流量爆增时redis集群扛不住了,这个时候怎么办呢,不能说把redis停了,我再搭建一个5主5从集群吧,这个时候就需要用到redis自动扩容…

Java反射机制与枚举类

目录 1.反射 1.1反射的定义与用途 1.2反射相关的类及方法 1.2.1(重要)获得类相关的方法 1.2.2(重要)获得类中属性相关的方法 1.2.3(重要)类中构造器相关的方法 1.2.4(重要)获得类中方法相关的方法 1.2.5获得类中注解相关的方法 1.3反射示例 1.3.1获取Class对象的三种…

实时采集MySQL数据之轻量工具Maxwell实操

文章目录概述定义原理Binlog说明Maxwell和Canal的区别部署安装MySQL准备初始化Maxwell元数据库Maxwell进程启动命令行参数配置文件实时监控Mysql输出KafkaKafka Topic分区控制实时监控MySQL指定表监控MySQL指定表同步全量数据概述 定义 Maxwell 官网地址 https://maxwells-dae…

3D相机获取点云信息的几种方法

在计算机中, 图像由一个个像素点组成。图像数据存储在每一个像素点中&#xff0c;每一个像素点包含了被测物体的信息。除了常见的RGB信息或者灰度信息以外&#xff0c;还可以包含深度信息和坐标等其它信息。在某个坐标系下的点的数据集又被称为点云。点云里的每一个点包含了丰富…

Windows取证——数据恢复(Fat32文件系统和NTFS文件系统)

目录 一、磁盘存储结构 &#xff08;一&#xff09;分区表 1.MBR 分区表&#xff08;主引导记录Master Boot Record &#xff0c; mbr&#xff09; 小实验&#xff1a;将磁盘改为活动分区 2.GPT 分区表&#xff08;全局唯一标识分区表GUID PartITion Table&#xff0c;gpt …

《SpringBoot篇》24.SpringBoot整合Freemarker超详细教程

陈老老老板&#x1f9b8;&#x1f468;‍&#x1f4bb;本文专栏&#xff1a;SpringBoot篇&#xff08;主要讲一些与springboot整合相关的内容&#xff09;&#x1f468;‍&#x1f4bb;本文简述&#xff1a;本文讲一下SpringBoot整合Freemarker的整合教程超详细教程。&#x1f…

SPL 和 SQL 能不能融合在一起?

文章目录SPL 和 SQL 能不能融合在一起&#xff1f;SPL资料SPL 和 SQL 能不能融合在一起&#xff1f; SQL和SPL都是面向结构化数据的通用处理技术。SQL普及率高受众广&#xff0c;很多用户天生就会用SQL查询数据&#xff0c;如果数据引擎支持SQL就会很容易上手&#xff0c;而且…

【MySQL】数据库约束与聚合查询和联合查询等进阶操作知识汇总

目录1.数据库约束:1.1 约束的类型:1.2 unique:1.3 primary key:1.3.1 分布式系统下,自增主键如何生成唯一id:1.4 foreign key:1.4.1 逻辑删除:2.表的设计/数据库的设计:2.1 数据库是如何设计的?3.进阶插入操作:4.进阶查询:4.1 聚合查询:4.2 group by 列名:4.3 联合查询/多表查…

2508. 添加边使所有节点度数都为偶数 c++

给你一个有 n 个节点的 无向 图&#xff0c;节点编号为 1 到 n 。再给你整数 n 和一个二维整数数组 edges &#xff0c;其中 edges[i] [ai, bi] 表示节点 ai 和 bi 之间有一条边。图不一定连通。 你可以给图中添加 至多 两条额外的边&#xff08;也可以一条边都不添加&#x…

C51——定时器控制寄存器

定时器的本质原理&#xff1a; 每经过一个一起周期&#xff0c;就加1 在寄存器里加 当我们想要操作寄存器的时候 就要找到TCON 当它开始数数的时候&#xff0c;会有天花板&#xff0c;会有溢出。 那我们怎么知道他溢出了&#xff1f;有TF0&#xff0c;当TF0 出现变化的时…

【剧前爆米花--爪哇岛寻宝】

作者&#xff1a;困了电视剧 专栏&#xff1a;《JavaSE语法与底层详解》 文章分布&#xff1a;这是一篇关于接口的文章&#xff0c;在本篇文章中我会将接口常用的一些实例进行讲解&#xff0c;以及部分方法在重写中的思想。 目录 Comparable和Comparator接口使用 Object类 t…

JDBC的简单使用与封装

目录 1、JDBC 2、JDBC的常用接口 1.Driver接口 2.Connection接口 3.Statement接口 4.ResultSet接口 3、JDBC的基本使用 1&#xff09;、简单的增删查改 Ⅰ、查 Ⅱ、增 Ⅲ、改 Ⅳ、删 2&#xff09;简单封装 1、JDBC 我们先了解JDBC是什么&#xff0c;JDBC的全称是Java数…