首页 > 编程语言 >Nacos 配置中心配置加载源码分析

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

时间:2024-07-25 17:25:06浏览次数:16  
标签:String dump 配置 cf Nacos 源码 groupKey LOG

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

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

标签:String,dump,配置,cf,Nacos,源码,groupKey,LOG
From: https://blog.csdn.net/weixin_42118323/article/details/140576015

相关文章

  • 爬虫爬取免费代理ip(附源码)
    在爬取某些网站的时候我们有可能遇到ip反爬措施,通常可以使用代理ip的方法应对,本次我们要爬取的是国内某知名的代理ip网站,并挑选出其中响应速度符合我们要求的IP。爬取代码如下:defget_ip(last):proxies_list=[]#ip池forpageinrange(1......
  • 最新nacos下载安装,版本号2.4.0,nacos持久化存储到本地mysql中,nacos配置登录账户密码
    官网:https://nacos.io/zh-cn/docs/quickstart/quick-start1.下载地址:https://github.com/alibaba/nacos/releases网络不好,很难下载。直接使用git克隆,然后自己编译,这是最快的了。克隆:gitclonehttps://github.com/alibaba/nacos.git然后到根目录中(我的:E:\Demos\nacos),执行cmd命令:mv......
  • 虚拟机环境下Rocky 9.4安装Mysql8并配置Dbeaver客户端
    环境:服务器操作系统:Rocky9.4数据库:mysql8.0.38客户端操作系统:windows10Dbeaver版本:24.1.3Rocky9.4安装镜像下载地址:https://mirrors.aliyun.com/rockylinux/9.4/isos/x86_64/Rocky-9.4-x86_64-dvd.isoDbeaver安装文件下载链接:https://dbeaver.io/files/dbeaver-c......
  • PixPro 全开源图床系统源码,非常强大的压缩率 自定义尺寸,支持多种格式
    PixPro全开源图床系统源码,非常强大的压缩率自定义尺寸,支持多种格式PixPro全开源图床系统源码,非常强大的压缩率自定义尺寸,支持多种格式一款专为个人需求设计的高效图床解决方案,集成了强大的图片压缩功能与优雅的前台后台管理界面。项目结构精简高效,提供自定义图片压缩率......
  • systemd service 配置 ulimit 限制
      在bash中,有个ulimit命令,提供了对shell及该shell启动的进程的可用资源控制。主要包括打开文件描述符数量、用户的最大进程数量、coredump文件的大小等。在CentOS5/6等版本中,资源限制的配置可以在/etc/security/limits.conf设置,针对root/user等各个用户或者......
  • linux到nacos获取配置
    #!/bin/bash#Nacos服务器地址nacos_server="http://xxx:8848"#命名空间ID(如果有)namespace_id="ecch-prod"#配置的DataIDdata_id="ecch.yaml"#配置的Groupgroup="ecch_prod"#Nacos认证信息(如果需要)#username="your-username"#passw......
  • 新版saas餐饮外卖小程序源码通用小程序,扫码点餐,DIY装修,代付,收银台,全开源
    新版saas餐饮外卖小程序源码通用小程序,扫码点餐,DIY装修,代付,收银台,全开源新版saas餐饮外卖小程序源码通用小程序,扫码点餐,DIY装修,代付,收银台,全开源使用场景:百货超市/奶茶店/加盟餐饮店/蔬菜店/零食店/咖啡店/水果店/便利店/蛋糕店通用小程序。营销玩法:优惠券、满额立减......
  • Nuxt.js 环境变量配置与使用
    title:Nuxt.js环境变量配置与使用date:2024/7/25updated:2024/7/25author:cmdragonexcerpt:摘要:“该文探讨了Nuxt.js框架下环境变量配置的详细过程,涉及.env文件配置、运行时访问、安全性考量、在不同场景下的实践(如Vue应用、插件、服务器路由)及多环境配置下的最佳实......
  • Honeywell Touchpoint Plus 易于配置的壁挂式控制系统
    TouchpointPlus为您的小型气体检测仪系统带来全新的控制体验。TouchpointPlus是一种易于配置的壁挂式控制系统,最多可支持16个气体检测通道。它的模块化设计使您能够控制和配置各种应用所需的设备。 提供可靠的控制 -来自最值得信赖的气体检测专家的Honeywell可......
  • C#读取指定json配置文件
    在C#开发中,有时候我们需要从JSON文件中读取配置或数据。本文将介绍一个简单的方法,使用Newtonsoft.Json库来读取指定的JSON文件并进行反序列化操作。读取json配置文件的源码取自SqlSugar作者的ReZero开源项目:https://gitee.com/DotNetNext/ReZero1.准备工作首先,我们需要使用NuGe......