Nacos 配置中心配置加载源码分析

前言:上一篇我们分析 Nacos 配置中心服务端源码的时候,多次看到有去读取本地配置文件,那本地配置文件是何时加载的?本篇我们来进行详细分析。

Nacos 系列文章传送门:

Nacos 初步认识和 Nacos 部署细节

Nacos 配置管理模型 – 命名空间(Namespace)、配置分组(Group)和配置集ID(Data ID)

Nacos 注册中心和配置中心【实战】

服务启动何时触发 Nacos 的注册流程?

Nacos Client 端服务注册流程源码分析

Nacos Server 端服务注册流程源码分析

Nacos 服务发现(订阅)源码分析(客户端)

Nacos 服务发现(订阅)源码分析(服务端)

Nacos Server 是如何通知 Nacos Client 服务下线?

Nacos Client 是如何接受 Nacos Server 推送的数据?

Nacos 故障转移源码分析(FailoverReactor)

Nacos 集群数据同步源码分析

Nacos 配置中心 Client 端配置热更新源码分析

Nacos 配置中心 Server 端源码分析

本地配置的加载

Nacos 本地配置的加载无疑肯定是 Nacos Server 启动时候加载的,Nacos 本地配置的加载和 DumpService 有莫大的关系,翻看源码可以看到 DumpService 是一个抽象类,它有两个子类,分别是 EmbeddedDumpService 和 ExternalDumpService,接下来我们将根据这两个类来展开分析。

在这里插入图片描述

EmbeddedDumpService 和 ExternalDumpService 源码

关于 EmbeddedDumpService 和 ExternalDumpService 类,这里我们先做一个初步认识,各自动的功能及源码如下:

  • EmbeddedDumpService : 是用本地存储的处理类,本存储是基于 derby 数据库, 是一种内嵌式数据库,和 JVM 共享内存。
  • ExternalDumpService :使用外部存储的处理类,比如集群情况下使用 MySQL 做存储。
//本地存储 基于 derby 数据库 内嵌式数据库
@Conditional(ConditionOnEmbeddedStorage.class)
@Component
public class EmbeddedDumpService extends DumpService {
	//。。。。。。
}


//外部存储 mysql
@Conditional(ConditionOnExternalStorage.class)
@Component
public class ExternalDumpService extends DumpService {
	//。。。。。。
}

ExternalDumpService#init 方法源码解析

ExternalDumpService#init 方法被 @PostConstruct 注解修饰,因此 init方法会在 ExternalDumpService 类实例化后执行,而该方法有调用了 DumpService#dumpOperate 方法。


//com.alibaba.nacos.config.server.service.dump.ExternalDumpService#init
@PostConstruct
@Override
protected void init() throws Throwable {
	//转储操作
	dumpOperate(processor, dumpAllProcessor, dumpAllBetaProcessor, dumpAllTagProcessor);
}

EmbeddedDumpService#init 方法源码解析

EmbeddedDumpService#init 方法会判断是 Nacos 是单机模式还是集群模式,如果是单机模式,会直接调用 DumpService#dumpOperate 方法,完成配置文件加载到本地,如果是集群模式,默认获取 CP 协议,然后会先观察 Leader 节点是否有配置值,有值才会直接调用 DumpService#dumpOperate 方法,完成配置文件加载到本地。


//com.alibaba.nacos.config.server.service.dump.EmbeddedDumpService#init
@PostConstruct
@Override
protected void init() throws Throwable {
	//是否是单机模式
	if (EnvUtil.getStandaloneMode()) {
		//是  调用 DumpService#dumpOperate 方法
		dumpOperate(processor, dumpAllProcessor, dumpAllBetaProcessor, dumpAllTagProcessor);
		return;
	}
	//走到这里表示不是单机模式 也就是是集群模式
	//获取 CP 协议
	CPProtocol protocol = protocolManager.getCpProtocol();
	//异常
	AtomicReference<Throwable> errorReference = new AtomicReference<>(null);
	//等待文件转储完成的 CountDownLatch
	CountDownLatch waitDumpFinish = new CountDownLatch(1);
	
	// watch path => /nacos_config/leader/ has value ?
	//观察 leader 总的 nacos_config 是否有值
	Observer observer = new Observer() {
		
		@Override
		public void update(Observable o) {
			if (!(o instanceof ProtocolMetaData.ValueItem)) {
				return;
			}
			//获取到值
			final Object arg = ((ProtocolMetaData.ValueItem) o).getData();
			GlobalExecutor.executeByCommon(() -> {
				// must make sure that there is a value here to perform the correct operation that follows
				//为空 判断
				if (Objects.isNull(arg)) {
					return;
				}
				// Identify without a timeout mechanism
				//超时机制
				EmbeddedStorageContextUtils.putExtendInfo(Constants.EXTEND_NEED_READ_UNTIL_HAVE_DATA, "true");
				// Remove your own listening to avoid task accumulation
				//标识
				boolean canEnd = false;
				//自旋 重试读取数据
				for (; ; ) {
					try {
						//调用 DumpService#dumpOperate 方法
						dumpOperate(processor, dumpAllProcessor, dumpAllBetaProcessor, dumpAllTagProcessor);
						//删除掉当前观察者
						protocol.protocolMetaData()
								.unSubscribe(Constants.CONFIG_MODEL_RAFT_GROUP, MetadataKey.LEADER_META_DATA, this);
						//改变标识
						canEnd = true;
					} catch (Throwable ex) {
						if (!shouldRetry(ex)) {
							errorReference.set(ex);
							canEnd = true;
						}
					}
					//标识改变 结束自旋
					if (canEnd) {
						ThreadUtils.countDown(waitDumpFinish);
						break;
					}
					ThreadUtils.sleep(500L);
				}
				EmbeddedStorageContextUtils.cleanAllContext();
			});
		}
	};

	//继续订阅
	protocol.protocolMetaData()
			.subscribe(Constants.CONFIG_MODEL_RAFT_GROUP, MetadataKey.LEADER_META_DATA, observer);
	
	// We must wait for the dump task to complete the callback operation before
	// continuing with the initialization
	//waitDumpFinish.await()
	ThreadUtils.latchAwait(waitDumpFinish);
	
	// If an exception occurs during the execution of the dump task, the exception
	// needs to be thrown, triggering the node to start the failed process
	//异常处理
	final Throwable ex = errorReference.get();
	if (Objects.nonNull(ex)) {
		throw ex;
	}
}


DumpService#dumpOperate 方法源码解析

DumpService#dumpOperate 方法的作用是把 Nacos 配置信息转储到本地文件中,主要做了一下操作:

  • 创建导出任务,包括配置信息、beta、tag 任务。
  • 清除历史配置信息(本地存储和外部存储会调用不通的方法处理),采用了分页处理的方式,一次处理 1000 条。
  • 转储配置信息,也就是将配置信息写入到本地文件中(重点关注)。
  • 更新 beta、tag 缓存。
  • 异步线程 10个为一组合并配置信息数据。
  • 集群模式保存心跳文件到本地磁盘,然后启动三个定时任务,分别去更新配置信息、beta、tag。

//com.alibaba.nacos.config.server.service.dump.DumpService#dumpOperate
protected void dumpOperate(DumpProcessor processor, DumpAllProcessor dumpAllProcessor,
		DumpAllBetaProcessor dumpAllBetaProcessor, DumpAllTagProcessor dumpAllTagProcessor) throws NacosException {
	//转储文件
	String dumpFileContext = "CONFIG_DUMP_TO_FILE";
	//计时
	TimerContext.start(dumpFileContext);
	try {
		LogUtil.DEFAULT_LOG.warn("DumpService start");
		//导出所有配置信息任务
		Runnable dumpAll = () -> dumpAllTaskMgr.addTask(DumpAllTask.TASK_ID, new DumpAllTask());
		//导出所有beta任务
		Runnable dumpAllBeta = () -> dumpAllTaskMgr.addTask(DumpAllBetaTask.TASK_ID, new DumpAllBetaTask());
		//导出所有tag任务
		Runnable dumpAllTag = () -> dumpAllTaskMgr.addTask(DumpAllTagTask.TASK_ID, new DumpAllTagTask());
		//清除历史配置信息
		Runnable clearConfigHistory = () -> {
			LOGGER.warn("clearConfigHistory start");
			//本地存储 单机模式默认 true 集群模式只有 leader 节点才可以执行
			//外部存储 本机可执行
			if (canExecute()) {
				try {
					//获取 前6个小时的时间戳
					Timestamp startTime = getBeforeStamp(TimeUtils.getCurrentTime(), 24 * getRetentionDays());
					//根据时间查询历史数据总数
					int totalCount = persistService.findConfigHistoryCountByTime(startTime);
					if (totalCount > 0) {
						//分页处理
						int pageSize = 1000;
						int removeTime = (totalCount + pageSize - 1) / pageSize;
						LOGGER.warn(
								"clearConfigHistory, getBeforeStamp:{}, totalCount:{}, pageSize:{}, removeTime:{}",
								startTime, totalCount, pageSize, removeTime);
						while (removeTime > 0) {
							// delete paging to avoid reporting errors in batches
							//批量删除数据
							persistService.removeConfigHistory(startTime, pageSize);
							removeTime--;
						}
					}
				} catch (Throwable e) {
					LOGGER.error("clearConfigHistory error : {}", e.toString());
				}
			}
		};
		
		try {
			//转储配置信息 重点关注
			dumpConfigInfo(dumpAllProcessor);
			
			// update Beta cache
			//更新 beta 缓存
			LogUtil.DEFAULT_LOG.info("start clear all config-info-beta.");
			DiskUtil.clearAllBeta();
			if (persistService.isExistTable(BETA_TABLE_NAME)) {
				dumpAllBetaProcessor.process(new DumpAllBetaTask());
			}
			// update Tag cache
			//更新 tag 缓存
			LogUtil.DEFAULT_LOG.info("start clear all config-info-tag.");
			DiskUtil.clearAllTag();
			if (persistService.isExistTable(TAG_TABLE_NAME)) {
				dumpAllTagProcessor.process(new DumpAllTagTask());
			}
			
			// add to dump aggr
			//查找所有聚合组
			List<ConfigInfoChanged> configList = persistService.findAllAggrGroup();
			if (configList != null && !configList.isEmpty()) {
				//获取总数
				total = configList.size();
				//每 10 个为一组
				List<List<ConfigInfoChanged>> splitList = splitList(configList, INIT_THREAD_COUNT);
				//数据合并
				for (List<ConfigInfoChanged> list : splitList) {
					//合并数据的线程
					MergeAllDataWorker work = new MergeAllDataWorker(list);
					work.start();
				}
				LOGGER.info("server start, schedule merge end.");
			}
		} catch (Exception e) {
			LogUtil.FATAL_LOG
					.error("Nacos Server did not start because dumpservice bean construction failure :\n" + e
							.toString());
			throw new NacosException(NacosException.SERVER_ERROR,
					"Nacos Server did not start because dumpservice bean construction failure :\n" + e.getMessage(),
					e);
		}
		//非单节点模式
		if (!EnvUtil.getStandaloneMode()) {
			//保存心跳文件到磁盘
			Runnable heartbeat = () -> {
				String heartBeatTime = TimeUtils.getCurrentTime().toString();
				// write disk
				try {
					//保存心跳文件到磁盘
					DiskUtil.saveHeartBeatToDisk(heartBeatTime);
				} catch (IOException e) {
					LogUtil.FATAL_LOG.error("save heartbeat fail" + e.getMessage());
				}
			};
			//10秒执行一次
			ConfigExecutor.scheduleConfigTask(heartbeat, 0, 10, TimeUnit.SECONDS);
			//获取初始延时时间随机数
			long initialDelay = new Random().nextInt(INITIAL_DELAY_IN_MINUTE) + 10;
			LogUtil.DEFAULT_LOG.warn("initialDelay:{}", initialDelay);
			//首次延迟随机时间 后每6小时保存一次所有配置文件
			ConfigExecutor.scheduleConfigTask(dumpAll, initialDelay, DUMP_ALL_INTERVAL_IN_MINUTE, TimeUnit.MINUTES);
			//首次延迟随机时间 后每6小时保存一次所有Beta缓存
			ConfigExecutor
					.scheduleConfigTask(dumpAllBeta, initialDelay, DUMP_ALL_INTERVAL_IN_MINUTE, TimeUnit.MINUTES);
			//首次延迟随机时间 后每6小时保存一次配置标签缓存
			ConfigExecutor
					.scheduleConfigTask(dumpAllTag, initialDelay, DUMP_ALL_INTERVAL_IN_MINUTE, TimeUnit.MINUTES);
		}
		//延迟10 分钟 每10分钟执行一次清除配置历史信息
		ConfigExecutor.scheduleConfigTask(clearConfigHistory, 10, 10, TimeUnit.MINUTES);
	} finally {
		TimerContext.end(dumpFileContext, LogUtil.DUMP_LOG);
	}
	
}

DumpService#dumpConfigInfo 方法源码解析

DumpService#dumpConfigInfo 方法主要判断是否需要全量转储配置文件,如果最后一次全量转储的事件戳小于6小时,则不需要全量转储,否则全量转储配置文件,我们重点关注 process 方法。

//com.alibaba.nacos.config.server.service.dump.DumpService#dumpConfigInfo
private void dumpConfigInfo(DumpAllProcessor dumpAllProcessor) throws IOException {
	int timeStep = 6;
	//全部转储标识
	Boolean isAllDump = true;
	// initial dump all
	//文件输入流
	FileInputStream fis = null;
	//最后一次全量导出时间戳
	Timestamp heartheatLastStamp = null;
	try {
		if (isQuickStart()) {
			//读取心跳文件
			File heartbeatFile = DiskUtil.heartBeatFile();
			if (heartbeatFile.exists()) {
				//心跳文件转换为文件输入流
				fis = new FileInputStream(heartbeatFile);
				//最后一次全量导出时间戳
				String heartheatTempLast = IoUtils.toString(fis, Constants.ENCODE);
				heartheatLastStamp = Timestamp.valueOf(heartheatTempLast);
				//最后一次全量导出时间戳是否小于6小时
				if (TimeUtils.getCurrentTime().getTime() - heartheatLastStamp.getTime()
						< timeStep * 60 * 60 * 1000) {
					isAllDump = false;
				}
			}
		}
		//如果最后一次全量转储的事件戳小于6小时 则不需要全量转储
		if (isAllDump) {
			//需要全量操作
			LogUtil.DEFAULT_LOG.info("start clear all config-info.");
			//清除所有数据
			DiskUtil.clearAll();
			//重新导出所有的配置文件数据
			dumpAllProcessor.process(new DumpAllTask());
		} else {
			//无需全量操作
			//获取上次转储的时间戳
			Timestamp beforeTimeStamp = getBeforeStamp(heartheatLastStamp, timeStep);
			//转储修改处理器
			DumpChangeProcessor dumpChangeProcessor = new DumpChangeProcessor(this, beforeTimeStamp,
					TimeUtils.getCurrentTime());
			//部分导出
			dumpChangeProcessor.process(new DumpChangeTask());
			//每12 小时执行一次 MD5 值比较
			Runnable checkMd5Task = () -> {
				LogUtil.DEFAULT_LOG.error("start checkMd5Task");
				List<String> diffList = ConfigCacheService.checkMd5();
				for (String groupKey : diffList) {
					String[] dg = GroupKey.parseKey(groupKey);
					String dataId = dg[0];
					String group = dg[1];
					String tenant = dg[2];
					//查询配置信息
					ConfigInfoWrapper configInfo = persistService.queryConfigInfo(dataId, group, tenant);
					//转储修改的配置
					ConfigCacheService.dumpChange(dataId, group, tenant, configInfo.getContent(),
							configInfo.getLastModified());
				}
				LogUtil.DEFAULT_LOG.error("end checkMd5Task");
			};
			ConfigExecutor.scheduleConfigTask(checkMd5Task, 0, 12, TimeUnit.HOURS);
		}
	} catch (IOException e) {
		LogUtil.FATAL_LOG.error("dump config fail" + e.getMessage());
		throw e;
	} finally {
		if (null != fis) {
			try {
				//关闭流
				fis.close();
			} catch (IOException e) {
				LogUtil.DEFAULT_LOG.warn("close file failed");
			}
		}
	}
}



DumpAllProcessor#process 方法源码解析

DumpAllProcessor#process 方法全量转储配置文件到本次磁盘,这里也会采用分页模式处理,一次处理 1000 条数据,并且地白名单进行了处理,最终调用 ConfigCacheService#dump 方法完成文件转储。

//com.alibaba.nacos.config.server.service.dump.processor.DumpAllProcessor#process
@Override
public boolean process(NacosTask task) {
	//查找最大的配置id
	long currentMaxId = persistService.findConfigMaxId();
	//最后一次的配置id
	long lastMaxId = 0;
	while (lastMaxId < currentMaxId) {
		//分页查询配置信息 一次 1000条
		Page<ConfigInfoWrapper> page = persistService.findAllConfigInfoFragment(lastMaxId, PAGE_SIZE);
		if (page != null && page.getPageItems() != null && !page.getPageItems().isEmpty()) {
			//循环处理
			for (ConfigInfoWrapper cf : page.getPageItems()) {
				//获取配置id
				long id = cf.getId();
				//比较配置id 赋值
				lastMaxId = id > lastMaxId ? id : lastMaxId;
				if (cf.getDataId().equals(AggrWhitelist.AGGRIDS_METADATA)) {
					//聚合白名单
					AggrWhitelist.load(cf.getContent());
				}
				
				if (cf.getDataId().equals(ClientIpWhiteList.CLIENT_IP_WHITELIST_METADATA)) {
					//客户端白名单
					ClientIpWhiteList.load(cf.getContent());
				}
				
				if (cf.getDataId().equals(SwitchService.SWITCH_META_DATAID)) {
					//切换服务
					SwitchService.load(cf.getContent());
				}
				//开始转储
				boolean result = ConfigCacheService
						.dump(cf.getDataId(), cf.getGroup(), cf.getTenant(), cf.getContent(), cf.getLastModified(),
								cf.getType());
				
				final String content = cf.getContent();
				final String md5 = MD5Utils.md5Hex(content, Constants.ENCODE);
				LogUtil.DUMP_LOG.info("[dump-all-ok] {}, {}, length={}, md5={}",
						GroupKey2.getKey(cf.getDataId(), cf.getGroup()), cf.getLastModified(), content.length(),
						md5);
			}
			DEFAULT_LOG.info("[all-dump] {} / {}", lastMaxId, currentMaxId);
		} else {
			lastMaxId += PAGE_SIZE;
		}
	}
	return true;
}



DumpChangeProcessor#process 方法源码解析

DumpChangeProcessor#process 方法是转储部分配置信息的实现方法,主要做了一下几件事情:

  • 找出更新的配置信息,发布 LocalDataChangeEvent 事件。
  • 找出需要删除的配置信息,删除并发布 LocalDataChangeEvent 事件。
  • 对修改了的配置信息调用 ConfigCacheService#dumpChange 方法进行转储操作,并刷新配置信息。
//com.alibaba.nacos.config.server.service.dump.processor.DumpChangeProcessor#process
@Override
public boolean process(NacosTask task) {
	LogUtil.DEFAULT_LOG.warn("quick start; startTime:{},endTime:{}", startTime, endTime);
	LogUtil.DEFAULT_LOG.warn("updateMd5 start");
	//更新 md5
	long startUpdateMd5 = System.currentTimeMillis();
	List<ConfigInfoWrapper> updateMd5List = persistService.listAllGroupKeyMd5();
	LogUtil.DEFAULT_LOG.warn("updateMd5 count:{}", updateMd5List.size());
	//遍历所有需要更新的数据
	for (ConfigInfoWrapper config : updateMd5List) {
		final String groupKey = GroupKey2.getKey(config.getDataId(), config.getGroup());
		//执行更新 发布 LocalDataChangeEvent 事件
		ConfigCacheService.updateMd5(groupKey, config.getMd5(), config.getLastModified());
	}
	//最后一次更新的事件
	long endUpdateMd5 = System.currentTimeMillis();
	LogUtil.DEFAULT_LOG.warn("updateMd5 done,cost:{}", endUpdateMd5 - startUpdateMd5);
	
	LogUtil.DEFAULT_LOG.warn("deletedConfig start");
	//删除配置
	long startDeletedConfigTime = System.currentTimeMillis();
	List<ConfigInfo> configDeleted = persistService.findDeletedConfig(startTime, endTime);
	LogUtil.DEFAULT_LOG.warn("deletedConfig count:{}", configDeleted.size());
	for (ConfigInfo configInfo : configDeleted) {
		if (persistService.findConfigInfo(configInfo.getDataId(), configInfo.getGroup(), configInfo.getTenant())
				== null) {
			//删除配置
			ConfigCacheService.remove(configInfo.getDataId(), configInfo.getGroup(), configInfo.getTenant());
		}
	}
	long endDeletedConfigTime = System.currentTimeMillis();
	LogUtil.DEFAULT_LOG.warn("deletedConfig done,cost:{}", endDeletedConfigTime - startDeletedConfigTime);
	
	LogUtil.DEFAULT_LOG.warn("changeConfig start");
	final long startChangeConfigTime = System.currentTimeMillis();
	List<ConfigInfoWrapper> changeConfigs = persistService.findChangeConfig(startTime, endTime);
	LogUtil.DEFAULT_LOG.warn("changeConfig count:{}", changeConfigs.size());
	//修改了的配置
	for (ConfigInfoWrapper cf : changeConfigs) {
		//修改了的配置 执行 dump 操作
		boolean result = ConfigCacheService
				.dumpChange(cf.getDataId(), cf.getGroup(), cf.getTenant(), cf.getContent(), cf.getLastModified());
		final String content = cf.getContent();
		final String md5 = MD5Utils.md5Hex(content, Constants.ENCODE);
		LogUtil.DEFAULT_LOG.info("[dump-change-ok] {}, {}, length={}, md5={}",
				new Object[] {GroupKey2.getKey(cf.getDataId(), cf.getGroup()), cf.getLastModified(),
						content.length(), md5});
	}
	//刷新配置
	ConfigCacheService.reloadConfig();
	long endChangeConfigTime = System.currentTimeMillis();
	LogUtil.DEFAULT_LOG.warn("changeConfig done,cost:{}", endChangeConfigTime - startChangeConfigTime);
	return true;
}


ConfigCacheService#dump 方法源码解析

ConfigCacheService#dump方法会获取写锁,来保证线程安全和不被重复操作,获取锁成功后,则会获取 MD5 值进行比较,如果 MD5 值一致且配置文件存在,不做处理,否则会判断是否是本地读取,如果是将配置信息写入本地磁盘,更新 MD5值,发布 LocalDataChangeEvent 事件,并释放锁。


//com.alibaba.nacos.config.server.service.ConfigCacheService#dump
public static boolean dump(String dataId, String group, String tenant, String content, long lastModifiedTs,
		String type) {
	//获取 group
	String groupKey = GroupKey2.getKey(dataId, group, tenant);
	//groupKey 存在则更新 不存在加入到 CACHE
	CacheItem ci = makeSure(groupKey);
	//设置类型
	ci.setType(type);
	//获取写锁
	final int lockResult = tryWriteLock(groupKey);
	//写锁判断
	assert (lockResult != 0);
	
	if (lockResult < 0) {
		//获取写锁失败
		DUMP_LOG.warn("[dump-error] write lock failed. {}", groupKey);
		return false;
	}
	
	try {
		//获取 md5 值
		final String md5 = MD5Utils.md5Hex(content, Constants.ENCODE);
		
		if (md5.equals(ConfigCacheService.getContentMd5(groupKey)) && DiskUtil.targetFile(dataId, group, tenant).exists()) {
			//md5 值一样 且文件存在
			DUMP_LOG.warn("[dump-ignore] ignore to save cache file. groupKey={}, md5={}, lastModifiedOld={}, "
							+ "lastModifiedNew={}", groupKey, md5, ConfigCacheService.getLastModifiedTs(groupKey),
					lastModifiedTs);
		} else if (!PropertyUtil.isDirectRead()) {
			//进入 表示不是单机模式 也不是内嵌数据库
			//保存文件到本地磁盘
			DiskUtil.saveToDisk(dataId, group, tenant, content);
		}
		//更新 md5 值 发布 LocalDataChangeEvent 事件
		updateMd5(groupKey, md5, lastModifiedTs);
		return true;
	} catch (IOException ioe) {
		DUMP_LOG.error("[dump-exception] save disk error. " + groupKey + ", " + ioe.toString(), ioe);
		if (ioe.getMessage() != null) {
			String errMsg = ioe.getMessage();
			if (NO_SPACE_CN.equals(errMsg) || NO_SPACE_EN.equals(errMsg) || errMsg.contains(DISK_QUATA_CN) || errMsg
					.contains(DISK_QUATA_EN)) {
				// Protect from disk full.
				FATAL_LOG.error("磁盘满自杀退出", ioe);
				System.exit(0);
			}
		}
		return false;
	} finally {
		//释放写锁
		releaseWriteLock(groupKey);
	}
}

ConfigCacheService#dumpChange 方法源码解析

ConfigCacheService#dumpChange 方法会获取写锁,来保证线程安全和不被重复操作,获取锁成功后,会判断是否是本地读取,如果是则会获取 MD5 值进行比较,如果 MD5 值一致,不做处理,否则会将配置信息写入本地磁盘,更新会 MD5值,发布 LocalDataChangeEvent 事件,并释放锁。

//com.alibaba.nacos.config.server.service.ConfigCacheService#dumpChange
public static boolean dumpChange(String dataId, String group, String tenant, String content, long lastModifiedTs) {
	//获取分组
	final String groupKey = GroupKey2.getKey(dataId, group, tenant);
	//更新或者加入缓存 CACHE
	makeSure(groupKey);
	//获取写锁
	final int lockResult = tryWriteLock(groupKey);
	//获取锁结果不为 0  直接返回
	assert (lockResult != 0);
	
	if (lockResult < 0) {
		//获取锁结果小于 0  返回失败
		DUMP_LOG.warn("[dump-error] write lock failed. {}", groupKey);
		return false;
	}
	
	try {
		//md5 值
		final String md5 = MD5Utils.md5Hex(content, Constants.ENCODE);
		//单机模式 且内嵌数据库
		if (!PropertyUtil.isDirectRead()) {
			//进入 表示不是单机模式 也不是内嵌数据库
			//获取本地配置 md5
			String localMd5 = DiskUtil.getLocalConfigMd5(dataId, group, tenant);
			if (md5.equals(localMd5)) {
				//相等表示没有变化 不处理
				DUMP_LOG.warn("[dump-ignore] ignore to save cache file. groupKey={}, md5={}, lastModifiedOld={}, "
								+ "lastModifiedNew={}", groupKey, md5, ConfigCacheService.getLastModifiedTs(groupKey),
						lastModifiedTs);
			} else {
				//MD5 不相等 则保存到磁盘中
				DiskUtil.saveToDisk(dataId, group, tenant, content);
			}
		}
		//更新 MD5 发布 LocalDataChangeEvent 事件
		updateMd5(groupKey, md5, lastModifiedTs);
		return true;
	} catch (IOException ioe) {
		DUMP_LOG.error("[dump-exception] save disk error. " + groupKey + ", " + ioe.toString(), ioe);
		return false;
	} finally {
		//释放写锁
		releaseWriteLock(groupKey);
	}
}



至此,Nacos 配置中心配置何时加载到本地磁盘上的源码分析完毕,希望可以帮助到有需要的朋友。

如有不正确的地方请各位指出纠正。

  • 10
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Nacos配置中心控制台的源码分析可以帮助我们深入理解其实现细节和工作原理。以下是一个大致的源码分析过程: 1. 入口类分析:首先,我们需要找到Nacos配置中心控制台的入口类。该类通常是一个Spring Boot应用的启动类,负责初始化和启动整个应用。我们可以查找包含main方法的类,或者在启动脚本中找到应用的入口点。 2. 依赖分析:接下来,我们需要分析应用所依赖的第三方库和框架。查看应用的pom.xml文件或者build.gradle文件,可以获取到所依赖的各个库和对应版本。这些依赖通常包括Spring框架、Nacos客户端等。 3. 配置加载与解析:Nacos配置中心控制台需要加载和解析配置,包括数据库配置Nacos服务地址配置等。我们可以查找相关的配置文件或者代码片段,了解配置加载和解析过程。 4. 控制器与路由:控制台通常提供了一些Web接口供前端调用。我们可以查找控制器类,分析其中的方法和注解,了解各个接口的功能和路由规则。 5. 页面模板与前端交互:配置中心控制台通常包含一些页面模板和与前端的交互逻辑。我们可以查找相关的HTML、CSS和JavaScript文件,分析页面的结构和交互逻辑。 6. 调用Nacos API:控制台需要与Nacos服务器进行通信,调用Nacos的API获取和修改配置信息。我们可以查找相关的API调用,了解控制台是如何与Nacos服务器进行通信的。 通过以上分析,我们可以逐步了解Nacos配置中心控制台的实现细节和工作原理。需要注意的是,具体的源码分析过程会因项目结构和代码风格而有所不同。以上只是一个大致的指导,具体分析还需根据实际情况来进行。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值