Nacos源码学习计划-Day16-配置中心-加载远程配置源码解析
ZealSinger 发布于 阅读:66 技术文档
// 加载共享配置文件
loadSharedConfiguration(composite);
// 加载额外配置文件
loadExtConfiguration(composite);
// 加载自身应用配置文件
loadApplicationConfiguration(composite, dataIdPrefix, nacosConfigProperties, env);
那么接下来我们就进行一定的分析。
配置文件类型使用讲解
自身应用配置
Nacos配置中心的功能中,允许我们进行多样化的配置管理,对于不同的业务要求都能进行灵活的配置,我们可以来使用看看。
我们在代码中使用如下配置
spring
application
namestock-service
cloud
nacos
# 配置中心
config
server-addrhttp//xxxxx8848
这个是使用最简单的配置,只需要配置地址即可,在读取 Nacos 配置中心文件时,是通过微服务名称去加载的,所以我们只需要在 Nacos 后台创建一个 stock-service 配置文件就可以读取到

我们还可以指定文件类型(后缀)
spring
application
namestock-service
cloud
nacos
# 配置中心
config
server-addrhttp//xxxxx8848
# 配置文件类型
file-extensionyaml
加上文件类型之后,我们在Nacos中再次创建一个新的配置,原来那个不删除,就会发现优先使用带尾缀的配置文件

我们还可以指定环境,毕竟在正式开发中,我们肯定开发环境需要一套配置,测试环境也会需要一套配置,这个时候就能进行环境的区分
spring
application
namestock-service
profiles
# 测试环境
activetest
cloud
nacos
# 配置中心
config
server-addrhttp//xxxxx8848
# 配置文件类型
file-extensionyaml
这个时候在Nacos又新增一个带环境变量的配置文件,可以发现优先级更加高

到这里我们先总结一下,在同一个的namespace下,如刚刚上文所说会有三种情况来读取配置文件,并且存在优先级关系。默认的读取stock-service的配置文件优先级最低,这种是通过微服务名称去获取的,其次就是加上配置文件类型之后,会去读取指定文件类型的配置,比不加文件类型的配置文件优先级要高,最后我们通过指定项目环境,发现这种方式优先级是最高的。
共享配置
实际开发中,我们因为设备环境有限,可能会出现部分业务系统公用一个数据库,同一个中间件即共享配置
在Nacos中我们也可以实现共享配置的功能
spring
application
namestock-service
profiles
# 测试环境
activetest
cloud
nacos
# 配置中心
config
server-addrhttp//xxxxx8848
# 配置文件类型
file-extensionyaml
# 共享配置文件
shared-configs
dataIdcommon-mysql.yaml
groupDEFAULT_GROUP
# 中间件配置一般不需要刷新
refreshfalse
在 Nacos config 配置下可以指定shared-configs配置,这里是个数组类型,就表示可以配置多个共享配置文件,就可以把一些中间件配置管理起来。但是这里要注意的是,不要和自身应用配置文件配置重复,这样是不会生效的,因为自身应用配置文件比共享配置文件优先级要高(后续源码分析的时候会看到,上一篇讲的源码中也能看出来,共享配置是最先被加载,也就导致共享配置优先级是最低的),如下图:

当然,如果上述的自身应用配置和共享配置不能满足需求,还可以使用额外配置/扩展配置文件(extension-configs)
远程配置文件加载顺序源码分析
还是回到代码上来,上一篇中的提到的locate()方法
// 加载共享配置文件
loadSharedConfiguration(composite);
// 加载额外配置文件
loadExtConfiguration(composite);
// 加载自身应用配置文件
loadApplicationConfiguration(composite, dataIdPrefix, nacosConfigProperties, env);
首先从这里我们就可以知道加载优先级和配置优先级,对于Nacos而言,配置其实就是key-value的形式的数据存储且对于一些配置有且只能由一个,那么很明显,在不同的配置文件中如果出现了相同的配置项,自然是优先加载的权重越低,因为后加载的可以覆盖前面加载的
加载优先级
自身应用配置文件 < 额外配置文件 < 共享配置文件
配置优先级
自身应用配置文件 > 额外配置文件 > 共享配置文件
我们再来看看 loadApplicationConfiguration() 方法 ,加载自身应用配置文件的优先级源码如下:
private void loadApplicationConfiguration(
CompositePropertySource compositePropertySource, String dataIdPrefix,
NacosConfigProperties properties, Environment environment) {
String fileExtension = properties.getFileExtension();
String nacosGroup = properties.getGroup();
// 加载 微服务名 配置文件
loadNacosDataIfPresent(compositePropertySource, dataIdPrefix, nacosGroup,
fileExtension, true);
// 加载 微服务.后缀名 配置文件
loadNacosDataIfPresent(compositePropertySource,
dataIdPrefix + DOT + fileExtension, nacosGroup, fileExtension, true);
// 加载 微服务-环境变量名.后缀名 ,因为环境变量可以配置多个,所以这里是循环
for (String profile : environment.getActiveProfiles()) {
String dataId = dataIdPrefix + SEP1 + profile + DOT + fileExtension;
loadNacosDataIfPresent(compositePropertySource, dataId, nacosGroup,
fileExtension, true);
}
}
从上面代码来看以及我们上面截图中的使用情况来看,也是相匹配的。微服务-环境变量名.后缀名 这种形式的文件因为是最后加载,所以权重很大,会将前面的相同的配置项给覆盖
不管是获取什么类型的配置文件,最终都会走到 loadNacosDataIfPresent() 这个方法,而在这个方法里面最终会通过 HTTP 的方式去获取 Nacos 服务端的配置文件数据,请求的地址是:/v1/cs/configs,获取到数据之后会立马持久化到本地数据,我们接下来看看这个接口对应的逻辑,如何进行的处理
远程配置文件读取源码分析
根据路径,找到对应的ConfigController代码如下,可以看到,核心的方法是最后一行的inner.doGetConfig

我们点到这个方法内部去看,内容如下,注意大量代码来袭~,代码很多但是我们暂时不需要全部都看,直接看如下代码中注释的那个方法
/**
* Execute to get config API.
*/
public String doGetConfig(HttpServletRequest request, HttpServletResponse response, String dataId, String group,
String tenant, String tag, String clientIp) throws IOException, ServletException {
final String groupKey = GroupKey2.getKey(dataId, group, tenant);
String autoTag = request.getHeader("Vipserver-Tag");
String requestIpApp = RequestUtil.getAppName(request);
int lockResult = tryConfigReadLock(groupKey);
final String requestIp = RequestUtil.getRemoteIp(request);
boolean isBeta = false;
if (lockResult > 0) {
FileInputStream fis = null;
try {
String md5 = Constants.NULL;
long lastModified = 0L;
CacheItem cacheItem = ConfigCacheService.getContentCache(groupKey);
if (cacheItem != null) {
if (cacheItem.isBeta()) {
if (cacheItem.getIps4Beta().contains(clientIp)) {
isBeta = true;
}
}
final String configType =
(null != cacheItem.getType()) ? cacheItem.getType() : FileTypeEnum.TEXT.getFileType();
response.setHeader("Config-Type", configType);
FileTypeEnum fileTypeEnum = FileTypeEnum.getFileTypeEnumByFileExtensionOrFileType(configType);
String contentTypeHeader = fileTypeEnum.getContentType();
response.setHeader(HttpHeaderConsts.CONTENT_TYPE, contentTypeHeader);
}
// file即配置文件
File file = null;
ConfigInfoBase configInfoBase = null;
PrintWriter out = null;
if (isBeta) {
md5 = cacheItem.getMd54Beta();
lastModified = cacheItem.getLastModifiedTs4Beta();
if (PropertyUtil.isDirectRead()) {
configInfoBase = persistService.findConfigInfo4Beta(dataId, group, tenant);
} else {
// 这里是第一次对于file进行复制,直接看这里即
file = DiskUtil.targetBetaFile(dataId, group, tenant);
}
response.setHeader("isBeta", "true");
........
}
targetBetaFile方法的源码如下,可以发现,其实就是从本地文件中去进行获取,而不是从数据库中,那么问题来了,服务器上的本地文件啥时候来的呢?
public static File targetTagFile(String dataId, String group, String tenant, String tag) {
File file = null;
if (StringUtils.isBlank(tenant)) {
file = new File(EnvUtil.getNacosHome(), TAG_DIR);
} else {
file = new File(EnvUtil.getNacosHome(), TENANT_TAG_DIR);
file = new File(file, tenant);
}
file = new File(file, group);
file = new File(file, dataId);
file = new File(file, tag);
return file;
}
这里就需要来说一下了,我们知道Nacos分为集群和单机模式,对于集群环境下,使用过的UU应该不陌生,集群环境下是必须要配置外部数据库的,其主要目的是为了方便集群中节点的一些中心化管理;那么在单机模式下,Nacos是不是就是0数据库存储依赖,完全依靠本地呢?答案是NO,在Nacos单机模式下,引入了Derby这个内嵌式数据库进行存储
对于集群环境下,大家应该比较好理解,一个节点Nacos重新进入集群,同步数据的时候,其实就会将数据保存到本地,集群中的节点本地磁盘上会出现配置信息很好理解,这个过程肯定是在Nacos启动的时候就会进行;那么对于Nacos单机环境下,其实也是一样的,大家可以自己找一下,定位到 DumpAllProcessor类中的process方法,在这个方法中主要干了两件事:先是去通过分页查询数据库中的config_info表数据,查询到数据之后,最终持久化到本地磁盘,其代码内容如下
// 分页查询配置信息
Page<ConfigInfoWrapper> page = persistService.findAllConfigInfoFragment(lastMaxId, PAGE_SIZE);
// 查询数据库
public Page<ConfigInfoWrapper> findAllConfigInfoFragment(final long lastMaxId, final int pageSize) {
String select = "SELECT id,data_id,group_id,tenant_id,app_name,content,md5,gmt_modified,type from config_info where id > ? order by id asc limit ?,?";
PaginationHelper<ConfigInfoWrapper> helper = createPaginationHelper();
try {
return helper.fetchPageLimit(select, new Object[] {lastMaxId, 0, pageSize}, 1, pageSize,
CONFIG_INFO_WRAPPER_ROW_MAPPER);
} catch (CannotGetJdbcConnectionException e) {
LogUtil.FATAL_LOG.error("[db-error] " + e.toString(), e);
throw e;
}
}
在上述的fetchPageLimit是接口PaginationHelper中定义的方法,PaginationHelper的两个实现EmbeddedPaginationHelperImpl和ExternalStoragePaginationHelperImply都是进行分页的,只是两者的分页逻辑不太一样,前者的内部是DatabaseOperate进行的数据库操作,也就是Derby这个数据库的操作类;后者使用的JdbcTemplate,对于JDBC大家肯定不陌生了,兼容多种第三方数据库
继续回到上述的process方法,读取完数据库后的操作如下,会进行写入本地磁盘的操作
// 把查询到配置写入到磁盘
boolean result = ConfigCacheService
.dump(cf.getDataId(), cf.getGroup(), cf.getTenant(), cf.getContent(), cf.getLastModified(),
cf.getType());
// 在 dump 方法中,会调用持久化到本地磁盘方法
DiskUtil.saveToDisk(dataId, group, tenant, content);
看到这里,我们 Nacos 服务端获取配置数据整个流程就串起来了。简单地说,就是客户端查询配置数据,服务端是直接获取的本地磁盘文件中的配置,而磁盘文件上的配置数据,是在服务端启动的时候会去查询数据库数据,然后持久化到磁盘上。
总结
本章主要讲解了在 Nacos 配置中心不同类型的配置文件的使用方式,尤其重要的是它们之间的优先级关系,并且分析了它们的源码实现。随后我们还分析了客户端在查询配置文件的时候,服务端是怎么处理的,主要就是读取本地磁盘文件。
大家也可以手动试一试,直接手动修改数据库中的配置信息,客户端会不会生效?这下知道了吧,并不是直接读取的数据库,所以自然不会立马生效。
还有一点就是,直接修改数据库是不会触发通知客户端进行变更事件的,在下一个章节中,我再来讲一讲客户端它是怎么感知配置文件变更的。
文章标题:Nacos源码学习计划-Day16-配置中心-加载远程配置源码解析
文章链接:https://www.zealsinger.xyz/?post=41
本站所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明来自ZealSinger !
如果觉得文章对您有用,请随意打赏。
您的支持是我们继续创作的动力!
微信扫一扫
支付宝扫一扫