一、配置储存

从整体上Nacos服务端的配置存储分为三层:

  • 内存:Nacos每个节点都在内存里缓存了配置,但是只包含配置的md5(缓存配置文件太多了),所以内存级别的配置只能用于比较配置是否发生了变更,只用于客户端长轮询配置等场景。
  • 文件系统:文件系统配置来源于数据库写入的配置。如果是集群启动或mysql单机启动,服务端会以本地文件系统的配置响应客户端查询。
  • 数据库:所有写数据都会先写入数据库。只有当以derby数据源(-DembeddedStorage=true)单机(-Dnacos.standalone=true)启动时,客户端的查询配置请求会实时查询derby数据库。

1.1.内存

对于写请求,Nacos会将数据先更新到数据库,之后异步写入所有节点的文件系统并更新内存。

CacheItem在Nacos服务端对应一个配置文件,缓存了配置的md5,持有一把读写锁控制访问冲突。

  1. public class CacheItem {
  2. // groupKey = namespace + group + dataId
  3. final String groupKey;
  4. // 配置md5
  5. public volatile String md5 = Constants.NULL;
  6. // 更新时间
  7. public volatile long lastModifiedTs;
  8. // nacos自己实现的简版读写锁
  9. public SimpleReadWriteLock rwLock = new SimpleReadWriteLock();
  10. // 配置文件类型:text/properties/yaml
  11. public String type;
  12. // ... 省略其他betaIp和tag相关属性
  13. }

ConfigCacheService一个重要的服务,负责管理所有内存配置CacheItem。

  1. public class ConfigCacheService {
  2. // 持久层服务(Derby或MySQL)
  3. private static PersistService persistService;
  4. // groupKey -> cacheItem.
  5. private static final ConcurrentHashMap<String, CacheItem> CACHE = new ConcurrentHashMap<String, CacheItem>();
  6. // 更新配置的md5
  7. public static void updateMd5(String groupKey, String md5, long lastModifiedTs) {
  8. CacheItem cache = makeSure(groupKey);
  9. if (cache.md5 == null || !cache.md5.equals(md5)) {
  10. cache.md5 = md5;
  11. cache.lastModifiedTs = lastModifiedTs;
  12. NotifyCenter.publishEvent(new LocalDataChangeEvent(groupKey));
  13. }
  14. }
  15. // 比较入参md5与缓存md5是否一致
  16. public static boolean isUptodate(String groupKey, String md5, String ip, String tag) {
  17. String serverMd5 = ConfigCacheService.getContentMd5(groupKey, ip, tag);
  18. return StringUtils.equals(md5, serverMd5);
  19. }
  20. // 获取缓存配置
  21. public static CacheItem getContentCache(String groupKey) {
  22. return CACHE.get(groupKey);
  23. }
  24. // 创建配置
  25. static CacheItem makeSure(final String groupKey) {
  26. CacheItem item = CACHE.get(groupKey);
  27. if (null != item) {
  28. return item;
  29. }
  30. CacheItem tmp = new CacheItem(groupKey);
  31. item = CACHE.putIfAbsent(groupKey, tmp);
  32. return (null == item) ? tmp : item;
  33. }
  34. // 获取配置读锁
  35. public static int tryReadLock(String groupKey) {
  36. CacheItem groupItem = CACHE.get(groupKey);
  37. int result = (null == groupItem) ? 0 : (groupItem.rwLock.tryReadLock() ? 1 : -1);
  38. return result;
  39. }
  40. // 释放配置读锁
  41. public static void releaseReadLock(String groupKey) {
  42. CacheItem item = CACHE.get(groupKey);
  43. if (null != item) {
  44. item.rwLock.releaseReadLock();
  45. }
  46. }
  47. }

1.2文件系统

Nacos刚启动时,内存中与文件系统中未必存在所有配置,所以DumpService会全量dump配置到文件系统与内存中。 另外当数据库配置发生变化时,也会dump到本地文件系统。

  1. // 启动时DumpService全量dump
  2. protected void dumpOperate(DumpProcessor processor, DumpAllProcessor dumpAllProcessor,
  3. DumpAllBetaProcessor dumpAllBetaProcessor, DumpAllTagProcessor dumpAllTagProcessor) throws NacosException {
  4. // 构造各类runnable任务...
  5. // 首次启动,dump数据库中所有配置到文件系统和内存中
  6. dumpConfigInfo(dumpAllProcessor);
  7. // 非单机部署,提交dump任务
  8. if (!EnvUtil.getStandaloneMode()) {
  9. ConfigExecutor.scheduleConfigTask(heartbeat, 0, 10, TimeUnit.SECONDS);
  10. // dump all config
  11. ConfigExecutor.scheduleConfigTask(dumpAll, initialDelay, DUMP_ALL_INTERVAL_IN_MINUTE, TimeUnit.MINUTES);
  12. // dump beta config
  13. ConfigExecutor
  14. .scheduleConfigTask(dumpAllBeta, initialDelay, DUMP_ALL_INTERVAL_IN_MINUTE, TimeUnit.MINUTES);
  15. // dump tag config
  16. ConfigExecutor
  17. .scheduleConfigTask(dumpAllTag, initialDelay, DUMP_ALL_INTERVAL_IN_MINUTE, TimeUnit.MINUTES);
  18. }
  19. ConfigExecutor.scheduleConfigTask(clearConfigHistory, 10, 10, TimeUnit.MINUTES);
  20. }

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数据源。

  1. // 指定数据源为mysql,直接返回mysql
  2. if (mysql) {
  3. return mysql;
  4. }
  5. // 如果单机部署,没指定数据源,使用derby
  6. if (standalone) {
  7. return derby;
  8. } else {
  9. // 如果集群部署且指定使用嵌入式存储,使用derby
  10. if (embeddedStorage) {
  11. return derby;
  12. } else {
  13. // 集群部署,默认使用mysql存储
  14. return mysql;
  15. }
  16. }

二、配置查询

GET /v1/cs/configs接口负责配置查询。