nacos 发布配置
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 发布配置的更多相关文章
- Nacos系列:基于Nacos的配置中心
前言 在看正文之前,我想请你回顾一下自己待过的公司都是怎么管理配置的,我想应该会有以下几种方式: 1.硬编码 没有什么配置不配置的,直接写在代码里面,比如使用常量类 优势:对开发友好,开发清楚地知道代 ...
- Nacos(四):SpringCloud项目中接入Nacos作为配置中心
前言 通过前两篇文章: Nacos(二):Nacos与OpenFeign的对接使用 Nacos(三):SpringCloud项目中接入Nacos作为注册中心 相信大家已经对Nacos作为注册中心的基本 ...
- Nacos(七):Nacos共享配置
前言 本文参考文章: SpringCloud Alibaba - Nacos Config 自定义共享配置 前景回顾: Nacos(六):多环境下如何"管理"及"隔离&q ...
- nacos统一配置中心源码解析
配置文件想必大家都很熟悉,无论什么架构 都离不开配置,虽然spring boot已经大大简化了配置,但如果服务很多 环境也好几个,管理配置起来还是很麻烦,并且每次改完配置都需要重启服务,nacos c ...
- 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 ...
- SpringBoot项目使用Nacos作为配置中心
前置条件:jdk.SpringBoot项目.Nacos.Linux服务器(可无) 具体版本:jdk11.SpringBoot 2.3.5.RELEASE.Nacos 2.0.3.Centos 6 目标 ...
- [3d跑酷] Xcode5 打包 发布配置
主题 Unity导出Xcode项目,使用Xocde打包ipa并提交到AppStore xcode发布配置 1.设置发布相关参数,比如 包名,版本,证书,ios设备版本 2.设置体系结构,支持的平台(I ...
- Spring Cloud Alibaba基础教程:使用Nacos作为配置中心
通过本教程的前两篇: <Spring Cloud Alibaba基础教程:使用Nacos实现服务注册与发现> <Spring Cloud Alibaba基础教程:支持的几种服务消费方 ...
- nacos-docker安装nacos并配置数据库
拉取nacos/nacos-server镜像 docker pull nacos/nacos-server 配置数据库(MySQL) 创建存储nacos配置的数据库 create database n ...
随机推荐
- Spring Boot自定义Redis缓存配置,保存value格式JSON字符串
Spring Boot自定义Redis缓存,保存格式JSON字符串 部分内容转自 https://blog.csdn.net/caojidasabi/article/details/83059642 ...
- ucore Lab2 实验笔记
ucore Lab2 lab 2 直接执行make qemu-nox会显示 assert 失败: kernel panic at kern/mm/default_pmm.c:277: assertio ...
- xrdp远程
安装图形界面 yum groupinstall "GNOME Desktop" 安装epel源 yum install epel* 安装xrdp yum --enablerepo= ...
- java数据结构03
1.求二叉树的深度 https://www.cnblogs.com/xudong-bupt/p/4036190.html class TreeNode { char val; TreeNode lef ...
- ubuntu 16.04 安装Opencv-3.2.0_GPU 与 opencv_contrib-3.2.0
1.准备依赖库 sudo apt-get install build-essential sudo apt-get install cmake git libgtk2.0-dev pkg-config ...
- 502 bad gateway nginx 的错误的解决方案
总结 一般是php问题居多,也需要调整相应的nginx参数,最后也可能是mysql假死 nginx问题 查看日志中的报错error.log一般设置路径/usr/local/nginx/logs/ngi ...
- bio,nio,aio的区别 select,poll,epoll的区别
先了解一些基本概念,什么是socket?什么是I/O操作 unix(like)世界里,一切皆文件,而文件是什么呢?文件就是一串二进制流而已,不管socket,还是FIFO.管道.终端,对我们来说,一切 ...
- Linux系统中的硬件问题如何排查?(4)
Linux系统中的硬件问题如何排查?(4) 2013-03-27 10:32 核子可乐译 51CTO.com 字号:T | T 在Linux系统中,对于硬件故障问题的排查可能是计算机管理领域最棘手的工 ...
- requests 模块例题示范
requests 模块 re模块和requests模块结合示范实例 .*? 不加圆括号表示在要匹配里面的内容不要: (.*?)表示在要匹配的两者之间的内容都要: import requests imp ...
- [51nod 1830] 路径交
问题描述 给定一棵n个点的树,以及m条路径,每次询问第L条到第R条路径的交集部分的长度(如果一条边同时出现在2条路径上,那么它属于路径的交集). 输入格式 第一行一个数n(n<=500,000) ...