源码篇--Nacos服务--中章(1):Nacos服务端的启动

文章目录

  • 前言
  • 一、Nacos Console 启动入口:
  • 二、启动过程:
    • 2.1 容器启动监听器:
      • 2.1.1 调整启动标识为正在启动状态:
      • 2.1.2 环境准备阶段:
      • 2.1.3 容器环境准备:
      • 2.1.4 自定义的环境变量 设置:
      • 2.1.5 服务启动:
      • 2.1.6 服务启动失败资源释放:
  • 总结


前言

在源码篇–Nacos服务–前章 我们对nacos 的架构及其概念进行了介绍,本文从源码层面对nacos 服务端资源的加载进行介绍;


一、Nacos Console 启动入口:

在这里插入图片描述

二、启动过程:

2.1 容器启动监听器:

Nacos 在服务启动的过程中,通过StartingApplicationListener 监听器来对服务启动进行的阶段 进行介入处理;

2.1.1 调整启动标识为正在启动状态:

private volatile boolean starting;
    
@Override
public void starting() {
    starting = true;
}

2.1.2 环境准备阶段:

 @Override
 public void environmentPrepared(ConfigurableEnvironment environment) {
     //  "logs", "conf", "data" 三个目录创建,如果没有设置 nacos.home 则取 C:\Users\Administrator\nacos
     makeWorkDir();
     // 环境上下文设置
     injectEnvironment(environment);
     // 加载自定义的配置配置文件,并监听目录下的 application.properties的文件修改
     loadPreProperties(environment);
     // 设置ip 及是否集群启动
     initSystemProperty();
 }

(1) 文件目录创建makeWorkDir:

private void makeWorkDir() {
   String[] dirNames = new String[] {"logs", "conf", "data"};
   	// 遍历创建文件目录
    for (String dirName : dirNames) {
        LOGGER.info("Nacos Log files: {}", Paths.get(EnvUtil.getNacosHome(), dirName));
        try {
        	// 强制创建文件目录  Paths.get(EnvUtil.getNacosHome() 获取系统设置的 nacos.home 路径 
        	// 如果没有设置取 当前系统用户空间的路径 C:\Users\Administrator\nacos
            DiskUtils.forceMkdir(new File(Paths.get(EnvUtil.getNacosHome(), dirName).toUri()));
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

(2) 环境上下文设置injectEnvironment:


private void injectEnvironment(ConfigurableEnvironment environment) {
	// 当前服务的环境sehzhi dao EnvUtil environment 的属性中
	//  private static ConfigurableEnvironment environment;
    EnvUtil.setEnvironment(environment);
}

(3) 加载并监听配置文件:
加载自定义的配置配置文件,并监听目录下的 application.properties的文件修改

private void loadPreProperties(ConfigurableEnvironment environment) {
    try {
        // 加载自定义的配置文件 路径为 spring.config.additional-location
        SOURCES.putAll(EnvUtil.loadProperties(EnvUtil.getApplicationConfFileResource()));
        // 加载 nacos_application_conf 配置文件
        environment.getPropertySources()
                .addLast(new OriginTrackedMapPropertySource(NACOS_APPLICATION_CONF, SOURCES));
        // 注册目录文件监听
        registerWatcher();
    } catch (Exception e) {
        throw new NacosRuntimeException(NacosException.SERVER_ERROR, e);
    }
}

文件监听 registerWatcher:

private void registerWatcher() throws NacosException {
  // 注册目录监听,最多可以监听16个目录 实现onChange  方法,文件在被修改时进行回调
    WatchFileCenter.registerWatcher(EnvUt il.getConfPath(), new FileWatcher() {
        @Override
        public void onChange(FileChangeEvent event) {
            // 目录下文件修改 回调事件
            try {
                // 加载自定义的配置文件
                Map<String, ?> tmp = EnvUtil.loadProperties(EnvUtil.getApplicationConfFileResource());
                SOURCES.putAll(tmp);
                // 发布配置文件变更事件,后续文章对nacos 的事件发布进行介绍
                NotifyCenter.publishEvent(ServerConfigChangeEvent.newEvent());
            } catch (IOException ignore) {
                LOGGER.warn("Failed to monitor file ", ignore);
            }
        }
        
        @Override
        public boolean interest(String context) {
        	// 对目录下哪个文件感兴趣
            return StringUtils.contains(context, "application.properties");
        }
    });
    
}

注册监听器 registerWatcher:

public static synchronized boolean registerWatcher(final String paths, FileWatcher watcher) throws NacosException {
    // 服务是否已经 closed 状态检查
    checkState();
    // 如果已经达到16个目录上限,直接返回
    if (NOW_WATCH_JOB_CNT == MAX_WATCH_FILE_JOB) {
        return false;
    }
    // 根据监控的路径 找到对应的监控器
    WatchDirJob job = MANAGER.get(paths);
    if (job == null) {
        // 监控器为空,说明这个目录之前没有被监控过
        job = new WatchDirJob(paths);
        //  WatchDirJob extends Thread 启动线程 执行任务
        job.start();
        //  Map<String, WatchDirJob> MANAGER = new HashMap<>(MAX_WATCH_FILE_JOB);
        // 放入缓存中
        MANAGER.put(paths, job);
        // 监控目录数量+1
        NOW_WATCH_JOB_CNT++;
    }
    // 当前监控目录 增加事件回调函数
    job.addSubscribe(watcher);
    return true;
}

文件监听 thread run:

 @Override
 public void run() {
     // 项目正常运行 进入while 死循环
     while (watch && !this.isInterrupted()) {
         try {
             // 获取监控事件
             final WatchKey watchKey = watchService.take();
             final List<WatchEvent<?>> events = watchKey.pollEvents();
             //  WatchKey的reset()方法用于重设WatchKey,以便继续监听文件系统的事件。
             //  当事件处理完毕后,必须调用reset()方法来重设WatchKey,否则监听器将无法继续监听文件系统事件
             watchKey.reset();
             if (callBackExecutor.isShutdown()) {
                 return;
             }
             if (events.isEmpty()) {
                 continue;
             }

             callBackExecutor.execute(() -> {
                 // 遍历事件
                 for (WatchEvent<?> event : events) {
                     // 取的事件
                     WatchEvent.Kind<?> kind = event.kind();

                     // Since the OS's event cache may be overflow, a backstop is needed
                     if (StandardWatchEventKinds.OVERFLOW.equals(kind)) {
                         eventOverflow();
                     } else {
                         // 事件处理
                         eventProcess(event.context());
                     }
                 }
             });
         } catch (InterruptedException | ClosedWatchServiceException ignore) {
             Thread.interrupted();
         } catch (Throwable ex) {
             LOGGER.error("An exception occurred during file listening : ", ex);
         }
     }
 }

事件处理 eventProcess:

 private void eventProcess(Object context) {
   // 构建问阿金事件变更对象
   final FileChangeEvent fileChangeEvent = FileChangeEvent.builder().paths(paths).context(context).build();
   final String str = String.valueOf(context);
   // 遍历所有的监控器
   for (final FileWatcher watcher : watchers) {
       // 调用监控器的interest  方法 ,如果返回true 说明改文件的改动,对应改文件目录的监控器
       if (watcher.interest(str)) {
           // 将对应的目录监控器 包装Runnable线程,onChange回调 FileWatcher 的onChange方法,进行业务处理
           Runnable job = () -> watcher.onChange(fileChangeEvent);
           // 获取目录监控器的 执行器
           Executor executor = watcher.executor();
           if (executor == null) {
               try {
                   // 执行器为空直接 运行线程
                   job.run();
               } catch (Throwable ex) {
                   LOGGER.error("File change event callback error : ", ex);
               }
           } else {
               // 执行器不为空交由 executor 执行线程
               executor.execute(job);
           }
       }
   }
}

(4) 本机ip & 是否集群启动属性设置:

 private void initSystemProperty() {
   if (EnvUtil.getStandaloneMode()) {
   		// 单机模式
        System.setProperty(MODE_PROPERTY_KEY_STAND_MODE, NACOS_MODE_STAND_ALONE);
    } else {
    	// 集群模式
        System.setProperty(MODE_PROPERTY_KEY_STAND_MODE, NACOS_MODE_CLUSTER);
    }
    if (EnvUtil.getFunctionMode() == null) {
        System.setProperty(MODE_PROPERTY_KEY_FUNCTION_MODE, DEFAULT_FUNCTION_MODE);
    } else if (EnvUtil.FUNCTION_MODE_CONFIG.equals(EnvUtil.getFunctionMode())) {
        System.setProperty(MODE_PROPERTY_KEY_FUNCTION_MODE, EnvUtil.FUNCTION_MODE_CONFIG);
    } else if (EnvUtil.FUNCTION_MODE_NAMING.equals(EnvUtil.getFunctionMode())) {
        System.setProperty(MODE_PROPERTY_KEY_FUNCTION_MODE, EnvUtil.FUNCTION_MODE_NAMING);
    }
    // 本机ip
    System.setProperty(LOCAL_IP_PROPERTY_KEY, InetUtils.getSelfIP());
}

是否单机判断:

/**
 * Standalone mode or not.
 */
public static boolean getStandaloneMode() {
    if (Objects.isNull(isStandalone)) {
        // 从 系统 环境里取 nacos.standalone 的值并转换 boolean   true 是单机,false 集群模式
        isStandalone = Boolean.getBoolean(Constants.STANDALONE_MODE_PROPERTY_NAME);
    }
    return isStandalone;
}

2.1.3 容器环境准备:

@Override
public void contextPrepared(ConfigurableApplicationContext context) {
     // 解析 服务端集群地址,只是进行了一次解析,并没有额外进行处理
     logClusterConf();
     // 如果集群模式下 开启SingleScheduledExecutorServic 每隔1s 输出一下 "Nacos is starting..."
     logStarting();
 }

集群配置文件解析:

private void logClusterConf() {
   // 集群模式下 对conf目录下的 cluster.conf 文件进行解析
   if (!EnvUtil.getStandaloneMode()) {
       try {
       		// 获取 cluster.conf  集群地址
           List<String> clusterConf = EnvUtil.readClusterConf();
           LOGGER.info("The server IP list of Nacos is {}", clusterConf);
       } catch (IOException e) {
           LOGGER.error("read cluster conf fail", e);
       }
   }
}

2.1.4 自定义的环境变量 设置:

@Override
public void contextLoaded(ConfigurableApplicationContext context) {
  // 自定义的环境变量 设置
  EnvUtil.customEnvironment();
}

2.1.5 服务启动:

@Override
public void started(ConfigurableApplicationContext context) {
   // 服务启动,将正在启动的标识starting 置为false
   starting = false;
   // 关闭SingleScheduledExecutorServic 每隔1s 输出一下 "Nacos is starting..."的 调度任务
   closeExecutor();
   // 设置服务启动标识
   ApplicationUtils.setStarted(true);
   // 输出服务 是否使用内嵌的存储 还是采用mysql 等外部存储
   judgeStorageMode(context.getEnvironment());
}

服务存储方式解析:

private void judgeStorageMode(ConfigurableEnvironment env) {
        
   // External data sources are used by default in cluster mode
    String platform = this.getDatasourcePlatform(env);
    // 获取是否使用额外的存储
    boolean useExternalStorage =
            !DEFAULT_DATASOURCE_PLATFORM.equalsIgnoreCase(platform) && !DERBY_DATABASE.equalsIgnoreCase(platform);
    
    // must initialize after setUseExternalDB
    // This value is true in stand-alone mode and false in cluster mode
    // If this value is set to true in cluster mode, nacos's distributed storage engine is turned on
    // default value is depend on ${nacos.standalone}
    
    if (!useExternalStorage) {
    	// 使用内嵌的 derby 存储
        boolean embeddedStorage = EnvUtil.getStandaloneMode() || Boolean.getBoolean("embeddedStorage");
        // If the embedded data source storage is not turned on, it is automatically
        // upgraded to the external data source storage, as before
        if (!embeddedStorage) {
            useExternalStorage = true;
        }
    }
    
    LOGGER.info("Nacos started successfully in {} mode. use {} storage",
            System.getProperty(MODE_PROPERTY_KEY_STAND_MODE),
            useExternalStorage ? DATASOURCE_MODE_EXTERNAL : DATASOURCE_MODE_EMBEDDED);
}

2.1.6 服务启动失败资源释放:

@Override
public void failed(ConfigurableApplicationContext context, Throwable exception) {
   // 启动失败,资源释放
   starting = false;
   
   makeWorkDir();
   
   LOGGER.error("Startup errors : ", exception);
   ThreadPoolManager.shutdown();
   WatchFileCenter.shutdown();
   NotifyCenter.shutdown();
   
   closeExecutor();
   
   context.close();
   
   LOGGER.error("Nacos failed to start, please see {} for more details.",
           Paths.get(EnvUtil.getNacosHome(), "logs/nacos.log"));
}

总结

本文从源码层面对nacos 服务端资源的加载进行介绍。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Spectre-v2 以及 Linux Retpoline技术简介

文章目录 前言一、Executive Summary1.1 Spectre-v2: Branch Predictor Poisoning1.2 Mitigating Spectre-v2 with Retpolines1.3 Retpoline Concept 二、BackgroundExploit Composition 三、(Un-)Directing Speculative Execution四、Construction (x86)4.1 Speculation Barri…

测试人员通常遇到的“坑”

网上看到一个帖子&#xff0c;从事多年的测试从业者&#xff0c;吐槽测试过程中遇到的“坑”&#xff0c;感觉比较有意思&#xff0c;我在工作当中也遇到通常的问题&#xff0c;看得出这位网友比较喜欢总结&#xff0c;帖子地址奉上&#xff0c;有兴趣的可以浏览一下&#xff1…

bug(警告):[vue-router] Duplicate named routes definition: …

查看警告&#xff1a;[vue-router] Duplicate named routes definition——翻译[vue-router]重复命名路由定义 小编劝诫&#xff1a;当我们在开发过程中警告也一定不要忽略&#xff0c;虽然你在本地跑代码时这些警告影响项目的正常运行&#xff0c;但是会让你产生误区&#xff…

大模型日报|今日必读的8篇大模型论文

大家好&#xff0c;今日必读的大模型论文来啦&#xff01; 1.EdgeFusion&#xff1a;端侧文本到图像生成&#xff0c;只需不到一秒 用于文本到图像生成的稳定扩散&#xff08;SD&#xff09;技术需要大量计算&#xff0c;这对其实际应用构成了重大障碍。为此&#xff0c;最近…

Oracle進階SQLDay03

一、函數進階復習 1、行轉列 select 用水儿量&#xff08;噸&#xff09; 统计项, sum(case when t_account.month01 then USENUM end) 一月, sum(case when t_account.month02 then USENUM end) 二月, sum(case when t_account.month03 then USENUM end) 三月, sum(case when …

STM32学习和实践笔记(15):STM32中断系统

中断概念 CPU执行程序时&#xff0c;由于发生了某种随机的事件(外部或内部)&#xff0c;引起CPU暂 时中断正在运行的程序&#xff0c;转去执行一段特殊的服务程序(中断服务子程序 或中断处理程序)&#xff0c;以处理该事件&#xff0c;该事件处理完后又返回被中断的程序 继…

飞桨Ai(二)paddle使用CPU版本可以正常识别,切换为GPU版本时无法识别结果

一、问题描述&#xff1a; 刚开始用paddle的CPU版本&#xff0c;对训练好的模型进行推理&#xff0c;正常识别出想要的结果后来尝试使用paddle的GPU版本&#xff0c;然后发现识别出来是空的 二、系统思路&#xff1a; 最终系统环境如下&#xff1a; 系统&#xff1a;win10 …

有哪些公认好用且免费的云渲染网渲平台?渲染100邀请码1a12

现在云渲染是越来越火了&#xff0c;无论是在建筑设计、影视动画还是效果图行业都有它的身影&#xff0c;云渲染能缩短制作周期&#xff0c;提高工作效率&#xff0c;那么市面上有哪些公认好用且免费的云渲染平台呢&#xff1f;这次我们来了解下。 首先&#xff0c;我们来看看有…

vulfocus靶场tomcat-cve_2017_12615 文件上传

7.0.0-7.0.81 影响版本 Windows上的Apache Tomcat如果开启PUT方法(默认关闭)&#xff0c;则存在此漏洞&#xff0c;攻击者可以利用该漏洞上传JSP文件&#xff0c;从而导致远程代码执行。 Tomcat 是一个小型的轻量级应用服务器&#xff0c;在中小型系统和并发访问用户不是很多…

「GO基础」在Windows上配置VS Code GO语言开发环境

&#x1f49d;&#x1f49d;&#x1f49d;欢迎莅临我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:「stormsha的主页」…

查看linux的主机配置脚本

废话不说 直接上指令 curl -Lso- bench.sh | bash 等待后&#xff0c;结果如图&#xff1a; 使用后没有问题&#xff0c;看情况使用 出事概不负责 介意勿用&#xff01;&#xff01;&#xff01;

LD-Pruner、EdgeFusion(On-Device T2I)、FreeDiff、TextCenGen、MemLLM

本文首发于公众号&#xff1a;机器感知 https://mp.weixin.qq.com/s/KiyNfwYWU-wBiCO-hE9qkA 苏 The devil is in the object boundary: towards annotation-free instance segmentation using Foundation Models Foundation models, pre-trained on a large amount of data…

Windows系统下安装paddle

开始使用_飞桨-源于产业实践的开源深度学习平台 (paddlepaddle.org.cn) 命令行下&#xff1a; python -m pip install --upgrade pip --user python -m pip install paddlepaddle2.6.1 -i https://pypi.tuna.tsinghua.edu.cn/simple 报异常 ERROR: Could not install packa…

Jmeter 测试Dubbo接口-实例

1、Dubbo插件准备 ①把jmeter-plugins-dubbo-2.7.4.1-jar-with-dependencies.jar包放在D:\apache-jmeter-5.5\lib\ext目录 ②重新打开Jmeter客户端 在线程组-添加-取样器-dubbo simple&#xff0c;添加dubbo接口请求 2、Jmeter测试lottery接口 ①配置zookeeper参数 由于dub…

windows和虚拟机互传文件

在虚拟机中设置共享文件夹 操作方法&#xff1a;打开VMware–>虚拟机–>设置–>选项–>共享文件夹&#xff08;见下图&#xff09;&#xff0c;大家在共享文件夹当中就可以把Windows当中的D盘或者其它盘共享到虚拟机中。比如我就是将D盘和E盘共享到了虚拟机中。 共…

【Vue】实现显示输入框字符长度

<div style"float: right; margin-right: 10px"><el-popover placement"top-start" width"200" trigger"hover" :content"当前输入的内容字节长度为&#xff1a; this.byteLength &#xff0c;剩余可输入的字节长度和最…

学校管网的仿写

工字形布局完成 效果 代码部分 在这里插入代码片 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" content"IEedge"><meta name"viewport…

某书Frida检测绕过记录

某书Frida检测绕过记录 前言Frida启动APPHook android_dlopen_ext查看加载的库分析libmsaoaidsec.soFrida检测绕过后记 前言 本来想要分析请求参数加密过程&#xff0c;结果发现APP做了Frida检测&#xff0c;于是记录一下绕过姿势(暴力但有用) Frida版本&#xff1a;16.2.1 AP…

ctfhub-ssrf(2)

1.URL Bypass 题目提示:请求的URL中必须包含http://notfound.ctfhub.com&#xff0c;来尝试利用URL的一些特殊地方绕过这个限制吧 打开环境发现URL中必须包含http://notfound.ctfhub.com&#xff0c;先按照之前的经验查看127.0.0.1/flag.php,发现没什么反应&#xff0c;按照题…

vue和react通用后台管理系统权限控制方案

1. 介绍 在任何企业级应用中&#xff0c;尤其是后台管理系统&#xff0c;权限控制是一个至关重要的环节。它确保了系统资源的安全性&#xff0c;防止非法访问和操作&#xff0c;保障业务流程的正常进行。本文件将详细解析后台管理系统中的权限控制机制及其实施策略。 那么权限…