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


前言

源码篇–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 服务端资源的加载进行介绍。

  • 23
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值