Nacos配置中心设计分析-客户端

  • 主要功能
  • 客户端初始化
  • 重要数据结构
    • NacosConfigService
    • EventDispatcher
    • ServerListManager
    • ClientWorker
    • CacheData
  • 典型场景
    • 获取配置文件内容
    • 发布配置文件内容
    • 添加配置文件监听
    • 删除配置文件监听
  • 线程模型
    • Nacos服务器登录线程
    • Nacos服务器列表更新线程
    • 客户端工作线程
    • 配置文件拉取线程
  • 版本

Nacos配置中心按照NameSpace、Group、DataId三级结构来组织配置文件,其中,NameSpace可以用于区分环境,例如dev、test等;Group是服务分组,可以用于标识应用;DataId是配置文件名。在实际应用中,可以将NameSpace和Group与应用绑定,固定写入应用的启动参数。

主要功能

Nacos配置中心客户端主要具有如下能力

  1. 获取配置文件内容
  2. 发布配置文件内容
  3. 添加配置文件监听
  4. 删除配置文件监听

客户端初始化

  • 初始化HTTP代理MetricsHttpAgent:对HttpAgent的进一步封装,增加了统计响应时间的能力,HttpAgent中维护了服务器列表,并启动一个线程不断向服务器发起登录请求,保证客户端的合法性。此外,如果没有指定服务器列表,还会启动一个线程不断查询服务器列表,如果服务器有变动,则发布服务器变更事件,并在本线程调用相应的监听器
  • 创建客户端工作线程ClientWorker:定时检查是否添加了需要监控的配置文件,如果有,则把这个文件更新的监控任务分配给拉取线程,不断从服务端拉取最新文件内容,如果发现文件变更,则更新客户端配置文件内容,并调用相应的监听器(如果有指定的线程池则使用该线程池,没有指定则用自身线程执行监听器调用)

初始化代码如下

    // NacosConfigServicepublic NacosConfigService(Properties properties) throws NacosException {String encodeTmp = properties.getProperty(PropertyKeyConst.ENCODE);if (StringUtils.isBlank(encodeTmp)) {encode = Constants.ENCODE;} else {encode = encodeTmp.trim();}initNamespace(properties);// 初始化HTTP代理MetricsHttpAgentagent = new MetricsHttpAgent(new ServerHttpAgent(properties));agent.start();// 创建客户端工作线程ClientWorkerworker = new ClientWorker(agent, configFilterChainManager, properties);}

重要数据结构

NacosConfigService

// NacosConfigService
public class NacosConfigService implements ConfigService {......// http代理MetricsHttpAgentprivate HttpAgent agent; // 客户端工作线程private ClientWorker worker;......
}

EventDispatcher

public class EventDispatcher {......// key是事件类型,value是监听器列表static final Map<Class<? extends AbstractEvent>, CopyOnWriteArrayList<AbstractEventListener>> LISTENER_MAP= new HashMap<Class<? extends AbstractEvent>, CopyOnWriteArrayList<AbstractEventListener>>();......
}

ServerListManager

// ServerListManager
public class ServerListManager {......// 服务列表(拼接成一行)private String serverAddrsStr;// 服务器列表(分解serverAddrsStr得到)volatile List<String> serverUrls = new ArrayList<String>();// 服务端点地址(查询服务列表请求地址)public String addressServerUrl;......
}

ClientWorker

// ClientWorker
public class ClientWorker {// 配置文件监听器,key是配置文件的标识,value是CacheDataprivate final AtomicReference<Map<String, CacheData>> cacheMap = new AtomicReference<Map<String, CacheData>>(new HashMap<String, CacheData>());
}

CacheData

// CacheData
public class CacheData {// 配置文件内容private volatile String content;// 监听器列表private final CopyOnWriteArrayList<ManagerListenerWrap> listeners;
}

CacheData是配置文件缓存,应用从CacheData中获取文件内容,CacheData由客户端线程不断拉取更新

典型场景

获取配置文件内容

优先从本地缓存文件读取配置文件内容,失败则从服务器获取,仍然失败,则从历史快照文件获取

    // NacosConfigServiceprivate String getConfigInner(String tenant, String dataId, String group, long timeoutMs) throws NacosException {...// 优先使用本地配置String content = LocalConfigInfoProcessor.getFailover(agent.getName(), dataId, group, tenant);if (content != null) {......configFilterChainManager.doFilter(null, cr);content = cr.getContent();......return content;}// 本地没有配置文件内容则请求服务端获取try {String[] ct = worker.getServerConfig(dataId, group, tenant, timeoutMs);cr.setContent(ct[0]);configFilterChainManager.doFilter(null, cr);content = cr.getContent();return content;            } catch (NacosException ioe) {if (NacosException.NO_RIGHT == ioe.getErrCode()) {throw ioe;}LOGGER.warn("[{}] [get-config] get from server error, dataId={}, group={}, tenant={}, msg={}",agent.getName(), dataId, group, tenant, ioe.toString());}// 从快照获取LOGGER.warn("[{}] [get-config] get snapshot ok, dataId={}, group={}, tenant={}, config={}", agent.getName(),dataId, group, tenant, ContentUtils.truncateContent(content));content = LocalConfigInfoProcessor.getSnapshot(agent.getName(), dataId, group, tenant);cr.setContent(content);configFilterChainManager.doFilter(null, cr);content = cr.getContent();return content;}

发布配置文件内容

调用API修改配置文件内容

添加配置文件监听

客户端定时拉取指定文件内容,如果发生变化,更新配置文件内容(存放在cacheMap里),并调用监听器进行相应处理。
增加文件监控代码如下

    // ClientWorkerpublic void checkConfigInfo() {// 分任务int listenerSize = cacheMap.get().size();// 向上取整为批数// ParamUtil.getPerTaskConfigSize()为每个线程处理的文件数目,添加文件监听时,会向cacheMap增加一条记录int longingTaskCount = (int) Math.ceil(listenerSize / ParamUtil.getPerTaskConfigSize());if (longingTaskCount > currentLongingTaskCount) {for (int i = (int) currentLongingTaskCount; i < longingTaskCount; i++) {// 要判断任务是否在执行 这块需要好好想想。 任务列表现在是无序的。变化过程可能有问题executorService.execute(new LongPollingRunnable(i));}currentLongingTaskCount = longingTaskCount;}}

拉取文件内容并更新代码如下

    // ClientWorkerclass LongPollingRunnable implements Runnable {private int taskId;public LongPollingRunnable(int taskId) {this.taskId = taskId;}@Overridepublic void run() {List<CacheData> cacheDatas = new ArrayList<CacheData>();List<String> inInitializingCacheList = new ArrayList<String>();try {// check failover configfor (CacheData cacheData : cacheMap.get().values()) {if (cacheData.getTaskId() == taskId) {cacheDatas.add(cacheData);try {checkLocalConfig(cacheData);if (cacheData.isUseLocalConfigInfo()) {cacheData.checkListenerMd5();}} catch (Exception e) {LOGGER.error("get local config info error", e);}}}// check server configList<String> changedGroupKeys = checkUpdateDataIds(cacheDatas, inInitializingCacheList);LOGGER.info("get changedGroupKeys:" + changedGroupKeys);for (String groupKey : changedGroupKeys) {String[] key = GroupKey.parseKey(groupKey);String dataId = key[0];String group = key[1];String tenant = null;if (key.length == 3) {tenant = key[2];}try {String[] ct = getServerConfig(dataId, group, tenant, 3000L);CacheData cache = cacheMap.get().get(GroupKey.getKeyTenant(dataId, group, tenant));// 更新配置文件内容cache.setContent(ct[0]);if (null != ct[1]) {cache.setType(ct[1]);}LOGGER.info("[{}] [data-received] dataId={}, group={}, tenant={}, md5={}, content={}, type={}",agent.getName(), dataId, group, tenant, cache.getMd5(),ContentUtils.truncateContent(ct[0]), ct[1]);} catch (NacosException ioe) {String message = String.format("[%s] [get-update] get changed config exception. dataId=%s, group=%s, tenant=%s",agent.getName(), dataId, group, tenant);LOGGER.error(message, ioe);}}for (CacheData cacheData : cacheDatas) {if (!cacheData.isInitializing() || inInitializingCacheList.contains(GroupKey.getKeyTenant(cacheData.dataId, cacheData.group, cacheData.tenant))) {// 调用监听器处理cacheData.checkListenerMd5();cacheData.setInitializing(false);}}inInitializingCacheList.clear();executorService.execute(this);} catch (Throwable e) {// If the rotation training task is abnormal, the next execution time of the task will be punishedLOGGER.error("longPolling error : ", e);executorService.schedule(this, taskPenaltyTime, TimeUnit.MILLISECONDS);}}}

删除配置文件监听

    // ClientWorkerpublic void removeListener(String dataId, String group, Listener listener) {group = null2defaultGroup(group);CacheData cache = getCache(dataId, group);if (null != cache) {cache.removeListener(listener);if (cache.getListeners().isEmpty()) {removeCache(dataId, group);}}}

线程模型

Nacos服务器登录线程

  • JVM中名称com.alibaba.nacos.client.config.security.updater
  • 定时向Nacos服务器发送登录请求,保证客户端的合法性
  • 实现类:ServerHttpAgent.匿名类

Nacos服务器列表更新线程

  • JVM中名称com.alibaba.nacos.client.Timer
  • 定时查询服务器列表(该线程只会在启动参数没有指定服务器列表的时候才会启动),如果有服务器变更,则更新客户端服务器列表信息,并调用该事件的监听器(如果有的话)
  • 实现类:ServerListManager.GetServerListTask

客户端工作线程

  • JVM中名称com.alibaba.nacos.client.Worker.{agentName}
  • 定时检查有没有新订阅的配置文件,如果有则启动一个拉取线程
  • 实现类:ClientWorker.匿名类

配置文件拉取线程

  • JVM中名称com.alibaba.nacos.client.Worker.longPolling.{agentName}
  • 定时拉取服务器配置文件内容,并依次调用该文件的监听器,如果添加监听器时指定了执行线程池,则使用指定线程线程池调用监听器
  • 实现类:ClientWorker.LongPollingRunnable

版本

本文基于Nacos的1.2.0版本

Nacos配置中心设计分析-客户端相关推荐

  1. 阿里面试这样问:Nacos配置中心交互模型是 push 还是 pull ?(原理+源码分析)...

    本文来源:公众号「 程序员内点事」 对于Nacos大家应该都不太陌生,出身阿里名声在外,能做动态服务发现.配置管理,非常好用的一个工具.然而这样的技术用的人越多面试被问的概率也就越大,如果只停留在使用 ...

  2. Nacos配置中心用法详细介绍

    上篇文章介绍了 Nacos 作为注册中心的用法,除此之外,Nacos 还能作为配置中心使用,那这篇文章就介绍下 Nacos 作为配置中心的基本用法,首先我们先了解下为什么需要使用配置中心. 一.为什么 ...

  3. Nacos配置中心介绍

    配置中心介绍 1.Spring Cloud Config Spring Cloud Config 为分布式系统的外部配置提供了服务端和客户端的支持方案.在配置的服务端您可以在所有环境中为应用程序管理外 ...

  4. Linux启动nacos成功日志_微服务系列之Nacos配置中心

    Nacos 介绍 Nacos 是 Alibaba 公司推出的开源工具,用于实现分布式系统的服务发现与配置管理.英文全称 Dynamic Naming and Configuration Service ...

  5. easyconnect获取服务端配置信息失败_图文解析 Nacos 配置中心的实现

    本文不会贴太多源码,基本靠图片和文字叙述 全文共 2582 字,预计阅读时间 12 分钟 什么是 Nacos 配置中心的架构 Nacos 使用示例 官方代码示例 Properties 解读 配置项的层 ...

  6. Alibaba Nacos配置中心功能介绍与不同命名空间、分组等配置

    概述:我们前面介绍过 Nacos 可以为我们提供服务注册与发现,以及实现了配置中心功能,本章将介绍nacos 配置中心的使用方法,以及其不同场景下的配置方式.在前面我们介绍过nacos的领域模型(下图 ...

  7. Nacos配置中心实战,盘古开发框架标配组件

    配置中心作为分布式微服务开发的标配组件,业界已有很多成功的典型应用,如:携程 Apollo 分布式配置中心.百度 Disconf 分布式配置中心等.盘古开发框架配置中心基于阿里的 Nacos 提供动态 ...

  8. hyperf接入阿里云nacos配置中心

    nacos官网的介绍 服务(Service)是 Nacos 世界的一等公民.Nacos 支持几乎所有主流类型的"服务"的发现.配置和管理: Nacos 致力于帮助您发现.配置和管理 ...

  9. 基于 Nacos 配置中心的动态日志配置方案

    log4j2 日志的级别不能落 SpringBoot 动态设置 logback 日志的级别 上面这两篇文章只是从技术角度说了,可以实现动态日志配置.但是并没有形成适用生产环境使用的方案.今天介绍一种基 ...

最新文章

  1. linux 命令常驻,Linux下任务调度的crond常驻命令
  2. Array和ArrayList区别
  3. Chromium 操作系统即将支持所有 SBC 单板电脑
  4. Ext js call方法
  5. mysql主从复制之异常解决--- Slave_IO_Running: NO
  6. ES6学习笔记二 新的声明方式和变量的解构赋值!
  7. 利用caffe的Python接口生成prototxt文件
  8. nginx 工作原理
  9. java Doc转Pdf
  10. matlab mallat算法,小波分解与重构1Mallat算法.PDF
  11. 征途服务器修改,特色修改之(国家任务篇)
  12. ubuntu美化--壁纸软件
  13. ios代码中的内存泄露,内存检测工具leaks 检测不出来
  14. 面试官最爱问的Redis(三)Redis的基本知识
  15. 2022年文化艺术品产权交易所研究报告
  16. oh my zsh 的alias文件
  17. 常用sql语句(备忘)
  18. PHP里把括号变成英文的方法,php怎么去除括号
  19. IDEA创建maven项目没有src/main/java目录问题
  20. 最全面的SpringMVC教程(六)——WebSocket

热门文章

  1. Unity笔记之WebGL下载图片、视频
  2. DayDreamSDK
  3. select下拉框 笔记
  4. Qt-FFmpeg开发-视频播放(3)
  5. windows apache php 500,Windows 环境下的 PHP5 与 Apache 服务_php
  6. cannot be hot swapped into a running virtal machine的解决方法
  7. 学习率(Learing Rate)的作用以及如何调整
  8. Speech and Language Processing (3rd ed. draft) Chapter 2 ——正则表达式,文本归一化,编辑距离 阅读笔记
  9. Java基本类型变量和引用类型变量
  10. c# 控件多屏显示全屏功能