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

news2024/9/20 2:11:08

前言:上一篇我们分析 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 配置中心配置何时加载到本地磁盘上的源码分析完毕,希望可以帮助到有需要的朋友。

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

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

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

相关文章

https改造-python https 改造

文章目录 前言https改造-python https 改造1.1. https 配置信任库2. 客户端带证书https发送,、服务端关闭主机、ip验证 前言 如果您觉得有用的话&#xff0c;记得给博主点个赞&#xff0c;评论&#xff0c;收藏一键三连啊&#xff0c;写作不易啊^ _ ^。   而且听说点赞的人每…

遗传算法与深度学习实战——进化深度学习

遗传算法与深度学习实战——进化深度学习 0. 前言1. 进化深度学习1.1 进化深度学习简介1.2 进化计算简介 2. 进化深度学习应用场景3. 深度学习优化3.1 优化网络体系结构 4. 通过自动机器学习进行优化4.1 自动机器学习简介4.2 AutoML 工具 5. 进化深度学习应用5.1 模型选择&…

Java给定一些元素随机从中选择一个

文章目录 代码实现java.util.Random类实现随机取数(推荐)java.util.Collections实现(推荐)Java 8 Stream流实现(不推荐) 完整代码参考&#xff08;含测试数据&#xff09; 在Java中&#xff0c;要从给定的数据集合中随机选择一个元素&#xff0c;我们很容易想到可以使用 java.…

【Stable Diffusion】(基础篇四)—— 模型

模型 本系列博客笔记主要参考B站nenly同学的视频教程&#xff0c;传送门&#xff1a;B站第一套系统的AI绘画课&#xff01;零基础学会Stable Diffusion&#xff0c;这绝对是你看过的最容易上手的AI绘画教程 | SD WebUI 保姆级攻略_哔哩哔哩_bilibili 本文主要讲解如何下载和使…

C++【泛型编程】【string类常用接口】学习

目录 泛型编程 推演实例化 显示实例化 类模板 类模板的声明和定义分离 STL string string的构造和拷贝构造 选取特定字符串拷贝 解析&#xff1a; 关于npos的解析 验证 从一个字符串中拷贝前几个字符 解析&#xff1a; 注意&#xff1a; 验证&#xff1a; size…

AI应用行业落地100例 | 移民公司Envoy Global引入AI员工赋能,效率飙升80%,开启服务新篇章

《AI应用行业落地100例》专题汇集了人工智能技术在金融、医疗、教育、制造等多个关键行业中的100个实际应用案例&#xff0c;深入剖析了AI如何助力行业创新、提升效率&#xff0c;并预测了技术发展趋势&#xff0c;旨在为行业决策者和创新者提供宝贵的洞察和启发。 Envoy Globa…

Pytorch使用教学2-Tensor的维度

在PyTorch使用的过程中&#xff0c;维度转换一定少不了。而PyTorch中有多种维度形变的方法&#xff0c;我们该在什么场景下使用什么方法呢&#xff1f; 本小节我们使用的张量如下&#xff1a; # 一维向量 t1 torch.tensor((1, 2)) # 二维向量 t2 torch.tensor([[1, 2, 3], …

【Unity PC端打包exe封装一个并添加安装引导】

Unity PC端打包exe封装一个并添加安装引导 比特虫在线制作ico图标ico图标转换工具 选中打包出来的所有文件和ico图标 右键 使用RAR软件 添加到压缩文件 两个名称要相同 设置完点击确认等待压缩完成 然后就可以使用 Smart Install Maker制作引导安装程序了

Matlab进阶绘图第64期—三维分组针状图

三维分组针状图可以看作是三维分组散点图的升级&#xff0c;能够直观地展示各组分、各元素的位置、对比情况。 由于Matlab中未收录三维分组针状图的绘制函数&#xff0c;因此需要大家自行设法解决。 本文使用自制的groupedstem3小工具进行三维分组针状图的绘制&#xff0c;先…

数据结构之深入理解简单选择排序:原理、实现与示例(C,C++)

文章目录 一、简单选择排序原理二、C/C代码实现总结&#xff1a; 在计算机科学中&#xff0c;排序算法是一种非常基础且重要的算法。简单选择排序&#xff08;Selection Sort&#xff09;作为其中的一种&#xff0c;因其实现简单、易于理解而受到许多初学者的喜爱。本文将详细介…

Maven概述

目录 1.Maven简介 2.Maven开发环境搭建 2.1下载Maven服务器 2.2安装&#xff0c;配置Maven 1.配置本地仓库地址 2.配置阿里云镜像地址 2.3在idea中配置maven 2.4在idea中创建maven项目 3.pom.xml配置 1.项目基本信息 2.依赖信息 3.构建信息 4.Maven命令 5.打包Jav…

华杉研发九学习日记17 正则表达式 异常

华杉研发九学习日记17 一&#xff0c;正则表达式 ^ $ 作用&#xff1a; 测试字符串内的模式(匹配) 例如&#xff0c;可以测试输入字符串&#xff0c;以查看字符串内是否出现电话号码模式或信用卡号码模式。这称为数据验证. 替换文本&#xff08;替换》 可以使用正则表达式来…

知识工程经典语言 PROLOG基本介绍

定义 PROLOG语言是一种基于Horn子句的逻辑型程序设计语言&#xff0c;也是一种陈述性语言。 PROLOG的语句 PROLOG语言仅有三种语句&#xff0c;称为事实、规则和问题。 事实 格式 <谓词名>(<项表>). 其中谓词名是以小写英文字母开头的字母、数字、下划线等组成的…

使用js实现常见的数据结构---链表,队列,栈,树

注&#xff1a;本文只作为数据结构的实现参考和个人理解 链表 链表是由多个节点&#xff08;node&#xff09;连接起来的&#xff0c;每个节点包含了一些存储的数据和指向下一个节点的指针&#xff0c; 链表&#xff1a;多个连续的节点构成&#xff0c;节点&#xff1a;包含一…

spring-boot3.x整合Swagger 3 (OpenAPI 3) +knife4j

1.简介 OpenAPI阶段的Swagger也被称为Swagger 3.0。在Swagger 2.0后&#xff0c;Swagger规范正式更名为OpenAPI规范&#xff0c;并且根据OpenAPI规范的版本号进行了更新。因此&#xff0c;Swagger 3.0对应的就是OpenAPI 3.0版本&#xff0c;它是Swagger在OpenAPI阶段推出的一个…

大数据-47 Redis 缓存过期 淘汰删除策略 LRU LFU 基础概念

点一下关注吧&#xff01;&#xff01;&#xff01;非常感谢&#xff01;&#xff01;持续更新&#xff01;&#xff01;&#xff01; 目前已经更新到了&#xff1a; Hadoop&#xff08;已更完&#xff09;HDFS&#xff08;已更完&#xff09;MapReduce&#xff08;已更完&am…

试过可道云teamOS的权限管理,才知道团队协作可以这么顺

在快节奏的工作环境中&#xff0c;团队协作的顺畅与否往往决定了项目的成败。作为团队中的一员&#xff0c;我深知权限管理在团队协作中的重要性。 我们的团队在协作过程中总是被权限问题所困扰。文件共享、资料访问、任务分配……每一个环节都需要小心翼翼地处理权限设置&…

学术研讨 | 区块链与隐私计算领域专用硬件研讨会顺利召开

学术研讨 近日&#xff0c;国家区块链技术创新中心主办&#xff0c;长安链开源社区支持的“区块链与隐私计算领域专用硬件研讨会”顺利召开&#xff0c;会议围绕基于区块链与隐私计算的生成式AI上链、硬件加速、软硬协同等主题展开讨论&#xff0c;来自复旦大学、清华大学、北京…

主题公园- 海豹主题式风格餐厅设计【AIGC应用】

业务背景&#xff1a;海洋馆针对细分客群增设一个打卡主题点位&#xff0c;以海豹主题式餐厅为打卡卖点&#xff0c;效果参见海豹主题式风格。 AIGC概念图制作平台&#xff1a;&#xff08;可灵&#xff09; https://klingai.kuaishou.com/ 关键词&#xff1a; 海豹主题餐厅…

机器学习 | 回归算法原理——随机梯度下降法

Hi&#xff0c;大家好&#xff0c;我是半亩花海。接着上次的多重回归继续更新《白话机器学习的数学》这本书的学习笔记&#xff0c;在此分享随机梯度下降法这一回归算法原理。本章的回归算法原理还是基于《基于广告费预测点击量》项目&#xff0c;欢迎大家交流学习&#xff01;…