一、配置储存
从整体上Nacos服务端的配置存储分为三层:
- 内存:Nacos每个节点都在内存里缓存了配置,但是只包含配置的md5(缓存配置文件太多了),所以内存级别的配置只能用于比较配置是否发生了变更,只用于客户端长轮询配置等场景。
- 文件系统:文件系统配置来源于数据库写入的配置。如果是集群启动或mysql单机启动,服务端会以本地文件系统的配置响应客户端查询。
- 数据库:所有写数据都会先写入数据库。只有当以derby数据源(-DembeddedStorage=true)单机(-Dnacos.standalone=true)启动时,客户端的查询配置请求会实时查询derby数据库。
1.1.内存
对于写请求,Nacos会将数据先更新到数据库,之后异步写入所有节点的文件系统并更新内存。
CacheItem在Nacos服务端对应一个配置文件,缓存了配置的md5,持有一把读写锁控制访问冲突。
public class CacheItem {// groupKey = namespace + group + dataIdfinal String groupKey;// 配置md5public volatile String md5 = Constants.NULL;// 更新时间public volatile long lastModifiedTs;// nacos自己实现的简版读写锁public SimpleReadWriteLock rwLock = new SimpleReadWriteLock();// 配置文件类型:text/properties/yamlpublic String type;// ... 省略其他betaIp和tag相关属性}
ConfigCacheService一个重要的服务,负责管理所有内存配置CacheItem。
public class ConfigCacheService {// 持久层服务(Derby或MySQL)private static PersistService persistService;// groupKey -> cacheItem.private static final ConcurrentHashMap<String, CacheItem> CACHE = new ConcurrentHashMap<String, CacheItem>();// 更新配置的md5public static void updateMd5(String groupKey, String md5, long lastModifiedTs) {CacheItem cache = makeSure(groupKey);if (cache.md5 == null || !cache.md5.equals(md5)) {cache.md5 = md5;cache.lastModifiedTs = lastModifiedTs;NotifyCenter.publishEvent(new LocalDataChangeEvent(groupKey));}}// 比较入参md5与缓存md5是否一致public static boolean isUptodate(String groupKey, String md5, String ip, String tag) {String serverMd5 = ConfigCacheService.getContentMd5(groupKey, ip, tag);return StringUtils.equals(md5, serverMd5);}// 获取缓存配置public static CacheItem getContentCache(String groupKey) {return CACHE.get(groupKey);}// 创建配置static CacheItem makeSure(final String groupKey) {CacheItem item = CACHE.get(groupKey);if (null != item) {return item;}CacheItem tmp = new CacheItem(groupKey);item = CACHE.putIfAbsent(groupKey, tmp);return (null == item) ? tmp : item;}// 获取配置读锁public static int tryReadLock(String groupKey) {CacheItem groupItem = CACHE.get(groupKey);int result = (null == groupItem) ? 0 : (groupItem.rwLock.tryReadLock() ? 1 : -1);return result;}// 释放配置读锁public static void releaseReadLock(String groupKey) {CacheItem item = CACHE.get(groupKey);if (null != item) {item.rwLock.releaseReadLock();}}}
1.2文件系统
Nacos刚启动时,内存中与文件系统中未必存在所有配置,所以DumpService会全量dump配置到文件系统与内存中。 另外当数据库配置发生变化时,也会dump到本地文件系统。
// 启动时DumpService全量dumpprotected void dumpOperate(DumpProcessor processor, DumpAllProcessor dumpAllProcessor,DumpAllBetaProcessor dumpAllBetaProcessor, DumpAllTagProcessor dumpAllTagProcessor) throws NacosException {// 构造各类runnable任务...// 首次启动,dump数据库中所有配置到文件系统和内存中dumpConfigInfo(dumpAllProcessor);// 非单机部署,提交dump任务if (!EnvUtil.getStandaloneMode()) {ConfigExecutor.scheduleConfigTask(heartbeat, 0, 10, TimeUnit.SECONDS);// dump all configConfigExecutor.scheduleConfigTask(dumpAll, initialDelay, DUMP_ALL_INTERVAL_IN_MINUTE, TimeUnit.MINUTES);// dump beta configConfigExecutor.scheduleConfigTask(dumpAllBeta, initialDelay, DUMP_ALL_INTERVAL_IN_MINUTE, TimeUnit.MINUTES);// dump tag configConfigExecutor.scheduleConfigTask(dumpAllTag, initialDelay, DUMP_ALL_INTERVAL_IN_MINUTE, TimeUnit.MINUTES);}ConfigExecutor.scheduleConfigTask(clearConfigHistory, 10, 10, TimeUnit.MINUTES);}
1.3数据库
配置文件主要存储在config_info表中。Nacos有三个配置项与数据源的选择有关:
- application.properties中的spring.datasource.platform配置项,默认为空,可以配置为mysql
- -Dnacos.standalone,true代表单机启动,false代表集群启动,默认false
- -DembeddedStorage,true代表使用嵌入式存储derby数据源,false代表不使用derby数据源,默认false
这块感觉比较乱,通过伪代码的方式理一下。主要是spring.datasource.platform在默认为空的场景下,满足条件集群启动且-DembeddedStorage=false(默认false),还是会选择mysql数据源。也就是说,集群启动,如果没特殊配置,Nacos会使用MySQL数据源。
// 指定数据源为mysql,直接返回mysqlif (mysql) {return mysql;}// 如果单机部署,没指定数据源,使用derbyif (standalone) {return derby;} else {// 如果集群部署且指定使用嵌入式存储,使用derbyif (embeddedStorage) {return derby;} else {// 集群部署,默认使用mysql存储return mysql;}}
二、配置查询
GET /v1/cs/configs接口负责配置查询。
