一、配置储存
从整体上Nacos服务端的配置存储分为三层:
- 内存:Nacos每个节点都在内存里缓存了配置,但是只包含配置的md5(缓存配置文件太多了),所以内存级别的配置只能用于比较配置是否发生了变更,只用于客户端长轮询配置等场景。
- 文件系统:文件系统配置来源于数据库写入的配置。如果是集群启动或mysql单机启动,服务端会以本地文件系统的配置响应客户端查询。
- 数据库:所有写数据都会先写入数据库。只有当以derby数据源(-DembeddedStorage=true)单机(-Dnacos.standalone=true)启动时,客户端的查询配置请求会实时查询derby数据库。
1.1.内存
对于写请求,Nacos会将数据先更新到数据库,之后异步写入所有节点的文件系统并更新内存。
CacheItem在Nacos服务端对应一个配置文件,缓存了配置的md5,持有一把读写锁控制访问冲突。
public class CacheItem {
// groupKey = namespace + group + dataId
final String groupKey;
// 配置md5
public volatile String md5 = Constants.NULL;
// 更新时间
public volatile long lastModifiedTs;
// nacos自己实现的简版读写锁
public SimpleReadWriteLock rwLock = new SimpleReadWriteLock();
// 配置文件类型:text/properties/yaml
public 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>();
// 更新配置的md5
public 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全量dump
protected 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 config
ConfigExecutor.scheduleConfigTask(dumpAll, initialDelay, DUMP_ALL_INTERVAL_IN_MINUTE, TimeUnit.MINUTES);
// dump beta config
ConfigExecutor
.scheduleConfigTask(dumpAllBeta, initialDelay, DUMP_ALL_INTERVAL_IN_MINUTE, TimeUnit.MINUTES);
// dump tag config
ConfigExecutor
.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,直接返回mysql
if (mysql) {
return mysql;
}
// 如果单机部署,没指定数据源,使用derby
if (standalone) {
return derby;
} else {
// 如果集群部署且指定使用嵌入式存储,使用derby
if (embeddedStorage) {
return derby;
} else {
// 集群部署,默认使用mysql存储
return mysql;
}
}
二、配置查询
GET /v1/cs/configs接口负责配置查询。