server 保留 2 份配置文件,一份在 mysql,一份在本地磁盘,同时在内存中缓存配置文件的 md5 值。当客户端获取配置时,server 直接返回本地磁盘文件,使用的是 sendFile api

FileInputStream fis = null;
fis.getChannel().transferTo(0L, fis.getChannel().size(), Channels.newChannel(response.getOutputStream()));

用户发布配置
ConfigController#publishConfig

// 更新数据库
persistService.insertOrUpdate(srcIp, srcUser, configInfo, time, configAdvanceInfo, false);
// 发布事件
EventDispatcher.fireEvent(new ConfigDataChangeEvent(false, dataId, group, tenant, time.getTime()));

整个事件流

public class EventDispatcher {

    /**
* add event listener
*/
static public void addEventListener(AbstractEventListener listener) {
for (Class<? extends Event> type : listener.interest()) {
getEntry(type).listeners.addIfAbsent(listener);
}
} /**
* fire event, notify listeners.
*/
static public void fireEvent(Event event) {
if (null == event) {
throw new IllegalArgumentException();
} for (AbstractEventListener listener : getEntry(event.getClass()).listeners) {
try {
listener.onEvent(event);
} catch (Exception e) {
log.error(e.toString(), e);
}
}
} /**
* For only test purpose
*/
static public void clear() {
LISTENER_HUB.clear();
} /**
* get event listener for eventType. Add Entry if not exist.
* 获取事件监听器,如果没有则新建 Entry
*/
static Entry getEntry(Class<? extends Event> eventType) {
for (; ; ) {
for (Entry entry : LISTENER_HUB) {
if (entry.eventType == eventType) {
return entry;
}
} Entry tmp = new Entry(eventType);
/**
* false means already exists
*/
if (LISTENER_HUB.addIfAbsent(tmp)) {
return tmp;
}
}
} // 把事件和监听器关联起来
static private class Entry {
final Class<? extends Event> eventType;
final CopyOnWriteArrayList<AbstractEventListener> listeners; Entry(Class<? extends Event> type) {
eventType = type;
listeners = new CopyOnWriteArrayList<AbstractEventListener>();
} @Override
public boolean equals(Object obj) {
if (null == obj || obj.getClass() != getClass()) {
return false;
}
if (this == obj) {
return true;
}
return eventType == ((Entry)obj).eventType;
} @Override
public int hashCode() {
return super.hashCode();
} } static private final Logger log = LoggerFactory.getLogger(EventDispatcher.class); static final CopyOnWriteArrayList<Entry> LISTENER_HUB = new CopyOnWriteArrayList<Entry>(); public interface Event {
} static public abstract class AbstractEventListener { public AbstractEventListener() {
// 执行 AsyncNotifyService 构造函数,把事件和监听器关联起来
EventDispatcher.addEventListener(this);
} /**
* 感兴趣的事件列表
*
* @return event list
*/
abstract public List<Class<? extends Event>> interest(); /**
* 处理事件
*
* @param event event
*/
abstract public void onEvent(Event event);
} }

AsyncNotifyService

@Service
public class AsyncNotifyService extends AbstractEventListener { @Override
public List<Class<? extends Event>> interest() {
List<Class<? extends Event>> types = new ArrayList<Class<? extends Event>>();
// 触发配置变更同步通知
types.add(ConfigDataChangeEvent.class);
return types;
} @Override
public void onEvent(Event event) { // 并发产生 ConfigDataChangeEvent
if (event instanceof ConfigDataChangeEvent) {
ConfigDataChangeEvent evt = (ConfigDataChangeEvent) event;
long dumpTs = evt.lastModifiedTs;
String dataId = evt.dataId;
String group = evt.group;
String tenant = evt.tenant;
String tag = evt.tag;
List<?> ipList = serverListService.getServerList(); // 其实这里任何类型队列都可以
Queue<NotifySingleTask> queue = new LinkedList<NotifySingleTask>();
for (int i = 0; i < ipList.size(); i++) {
queue.add(new NotifySingleTask(dataId, group, tenant, tag, dumpTs, (String) ipList.get(i), evt.isBeta));
}
EXECUTOR.execute(new AsyncTask(httpclient, queue));
}
}
}

由以上可见,ConfigDataChangeEvent 事件由 AsyncNotifyService.onEvent 负责

向集群中所有节点(包括自己)发送请求

/v1/cs/communication/dataChange?dataId=oo.yml&group=xx&tenant=dev

节点处理配置变更请求

CommunicationController#notifyConfigInfo

用数据库中的数据更新磁盘上的文件缓存

dumpService.dump(dataId, group, tenant, tag, lastModifiedTs, handleIp);
// DumpService#dump
public void dump(String dataId, String group, String tenant, String tag, long lastModified, String handleIp,
boolean isBeta) {
String groupKey = GroupKey2.getKey(dataId, group, tenant);
dumpTaskMgr.addTask(groupKey, new DumpTask(groupKey, tag, lastModified, handleIp, isBeta));
}
@Service
public class DumpService { @Autowired
private Environment env; @Autowired
PersistService persistService; @PostConstruct
public void init() {
LogUtil.defaultLog.warn("DumpService start");
DumpProcessor processor = new DumpProcessor(this);
dumpTaskMgr = new TaskManager("com.alibaba.nacos.server.DumpTaskManager");
// 设置默认处理器
dumpTaskMgr.setDefaultTaskProcessor(processor);
}
}

TaskManager

public TaskManager(String name) {
this.name = name;
if (null != name && name.length() > 0) {
this.processingThread = new Thread(new ProcessRunnable(), name);
} else {
this.processingThread = new Thread(new ProcessRunnable());
}
this.processingThread.setDaemon(true);
this.closed.set(false);
this.processingThread.start();
} class ProcessRunnable implements Runnable { @Override
public void run() {
while (!TaskManager.this.closed.get()) {
try {
Thread.sleep(100);
TaskManager.this.process();
} catch (Throwable e) {
}
} } } public void addTask(String type, AbstractTask task) {
this.lock.lock();
try {
AbstractTask oldTask = tasks.put(type, task);
MetricsMonitor.getDumpTaskMonitor().set(tasks.size());
if (null != oldTask) {
task.merge(oldTask);
}
} finally {
this.lock.unlock();
}
} protected void process() {
for (Map.Entry<String, AbstractTask> entry : this.tasks.entrySet()) {
AbstractTask task = null;
this.lock.lock();
try {
// 获取任务
task = entry.getValue();
if (null != task) {
if (!task.shouldProcess()) {
// 任务当前不需要被执行,直接跳过
continue;
}
// 先将任务从任务Map中删除
this.tasks.remove(entry.getKey());
MetricsMonitor.getDumpTaskMonitor().set(tasks.size());
}
} finally {
this.lock.unlock();
} if (null != task) {
// 获取任务处理器
TaskProcessor processor = this.taskProcessors.get(entry.getKey());
if (null == processor) {
// DumpTask 使用的是默认处理器,即 DumpProcessor
processor = this.getDefaultTaskProcessor();
}
if (null != processor) {
boolean result = false;
try {
// 处理任务
result = processor.process(entry.getKey(), task);
} catch (Throwable t) {
log.error("task_fail", "处理task失败", t);
}
if (!result) {
// 任务处理失败,设置最后处理时间
task.setLastProcessTime(System.currentTimeMillis()); // 将任务重新加入到任务Map中
this.addTask(entry.getKey(), task);
}
}
}
} if (tasks.isEmpty()) {
this.lock.lock();
try {
this.notEmpty.signalAll();
} finally {
this.lock.unlock();
}
}
}

因此执行的是 DumpProcessor#process,读取数据库中的配置,更新本地磁盘文件,同时生成 LocalDataChangeEvent 事件。

处理 LocalDataChangeEvent 事件

// com.alibaba.nacos.config.server.service.LongPollingService#onEvent
public void onEvent(Event event) {
if (isFixedPolling()) {
// ignore
} else {
if (event instanceof LocalDataChangeEvent) {
LocalDataChangeEvent evt = (LocalDataChangeEvent)event;
scheduler.execute(new DataChangeTask(evt.groupKey, evt.isBeta, evt.betaIps));
}
}
}

取消线程池中的定时任务,发送响应(变化的配置文件 id)给客户端

com.alibaba.nacos.config.server.service.LongPollingService.DataChangeTask#run

客观地讲,nacos 的代码细节不优雅,还在发展中。

nacos 发布配置的更多相关文章

  1. Nacos系列:基于Nacos的配置中心

    前言 在看正文之前,我想请你回顾一下自己待过的公司都是怎么管理配置的,我想应该会有以下几种方式: 1.硬编码 没有什么配置不配置的,直接写在代码里面,比如使用常量类 优势:对开发友好,开发清楚地知道代 ...

  2. Nacos(四):SpringCloud项目中接入Nacos作为配置中心

    前言 通过前两篇文章: Nacos(二):Nacos与OpenFeign的对接使用 Nacos(三):SpringCloud项目中接入Nacos作为注册中心 相信大家已经对Nacos作为注册中心的基本 ...

  3. Nacos(七):Nacos共享配置

    前言 本文参考文章: SpringCloud Alibaba - Nacos Config 自定义共享配置 前景回顾: Nacos(六):多环境下如何"管理"及"隔离&q ...

  4. nacos统一配置中心源码解析

    配置文件想必大家都很熟悉,无论什么架构 都离不开配置,虽然spring boot已经大大简化了配置,但如果服务很多 环境也好几个,管理配置起来还是很麻烦,并且每次改完配置都需要重启服务,nacos c ...

  5. spring-boot 2.5.4,nacos 作为配置、服务发现中心,Cloud Native Buildpacks 打包镜像,GitLab CI/CD

    spring-boot 2.5.4,nacos 作为配置.服务发现中心,Cloud Native Buildpacks 打包镜像,GitLab CI/CD 本文主要介绍 Java 通过 Cloud N ...

  6. SpringBoot项目使用Nacos作为配置中心

    前置条件:jdk.SpringBoot项目.Nacos.Linux服务器(可无) 具体版本:jdk11.SpringBoot 2.3.5.RELEASE.Nacos 2.0.3.Centos 6 目标 ...

  7. [3d跑酷] Xcode5 打包 发布配置

    主题 Unity导出Xcode项目,使用Xocde打包ipa并提交到AppStore xcode发布配置 1.设置发布相关参数,比如 包名,版本,证书,ios设备版本 2.设置体系结构,支持的平台(I ...

  8. Spring Cloud Alibaba基础教程:使用Nacos作为配置中心

    通过本教程的前两篇: <Spring Cloud Alibaba基础教程:使用Nacos实现服务注册与发现> <Spring Cloud Alibaba基础教程:支持的几种服务消费方 ...

  9. nacos-docker安装nacos并配置数据库

    拉取nacos/nacos-server镜像 docker pull nacos/nacos-server 配置数据库(MySQL) 创建存储nacos配置的数据库 create database n ...

随机推荐

  1. Proxy&Reflect

    大部分时候我们使用的都是前置代理, 即我们把直接和代理对象进行交互(所有操作都发生在代理对象身上)的方式叫做前置代理. 那什么是后置代理? 借助原型链机制, 我们直接和 obj 进行交互而不是和代理对 ...

  2. kali下纯文本与窗口环境切换

    切到纯文本环境,想返回 试了半天ctrl+alt+f7不行,  最后我想试试ctrl+alt+f8竟然成了: 而且那是之前以root账户登录图像界面时切换回去是f8,普通用户是f9 ,为何如此,我还不 ...

  3. CDN杂谈

    两大cdn公司:一个是Akamai,一个是LimeLight,所以有两个阵营 CDN在利用DNS的转授权来引导最终访问者找到最理想的缓存或者镜像点,他是一种基于域名的服务.在不同的实现方式下,最终的定 ...

  4. web页面请求历程

    web页面请求历程 1)准备DHCP,UDP,IP和以太网 客户端要访问www.google.com的网站. 首先客户端要与网络相接,没有IP地址地址就不能做什么事情,所以客户端采取的一个网络相关的动 ...

  5. 011-通过安装percona插件监控MySQL

    percona-monitoring-plugins是percona专门为MySQL监控的工具,支持Nagios,cacti,zabibx,本文主要介绍percona-monitoring-plugi ...

  6. Wannafly挑战赛22 C 多项式(大数,多项式极限)

    链接:https://ac.nowcoder.com/acm/contest/160/C 来源:牛客网 多项式 时间限制:C/C++ 1秒,其他语言2秒 空间限制:C/C++ 262144K,其他语言 ...

  7. 什么是lease机制?

    分布式系统理论之租约机制学习 一,租约机制介绍 在分布式系统中,往往会有一个中心服务器节点.该节点负责存储.维护系统中的元数据.如果系统中的各种操作都依赖于中心服务器上的元数据,那么中心服务器很容易成 ...

  8. 微信小程序横向滚动正确姿势

    <1>xml文件 <view> <scroll-view scroll-x class="scroll-header"> <view cl ...

  9. 【Linux】CentOS6上mysql5.7安装

    1.下载安装yum源 根据系统下载yum源 https://dev.mysql.com/downloads/repo/yum/ rpm -ivh xxxxx.rpm 2.修改yum源 vim /etc ...

  10. scrapy五大核心组件和中间件以及UA池和代理池

    五大核心组件的工作流程 引擎(Scrapy) 用来处理整个系统的数据流处理, 触发事务(框架核心) 调度器(Scheduler) 用来接受引擎发过来的请求, 压入队列中, 并在引擎再次请求的时候返回. ...