文章目录
- 前言
- 一、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 服务端资源的加载进行介绍。