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 ...
随机推荐
- 【总结】Android 应用测试总结
前提 所有的功能分支已完成 启动: 1. 启动入口:桌面正常启动,最近运行启动,所有程序列表中启动,锁屏快捷启动2. 其他入口:从其他程序开启应用,从外部以文件形式打开应用(如果有)3. 退回:从其他 ...
- 第98:svd原理
SVD分解:任何矩阵都可以分解成第一行的形式,3个相乘.UV都是正交矩阵,中间的是奇异值. 3个相乘的形式可以拆分.即奇异值*第一行*第一列.在相加. 奇异值有时很小,在这种情况下,丢掉,可以减少计算 ...
- Tensort之uff
# This sample uses a UFF MNIST model to create a TensorRT Inference Engine from random import randin ...
- Window10的激活步骤
1. 首先,我们先查看一下Win10正式专业版系统的激活状态: 点击桌面左下角的“Windows”按钮,从打开的扩展面板中依次点击“设置”-“更新和安全”,并切换到“激活”选项卡,在此就可以查看到当前 ...
- oracle partition 分区
--范围分区create table person( id int, name varchar2(20), birth date, sex char(2))partition by range (bi ...
- vs2017 mvc 启动时经常出现调用的目标发生异常
1.vs 2017 调试web 程序时老是出现调用的目标发生异常 本人眼拙,基本上看了网站说的一些方法,设置环境变量是无效的,只有一个办法,卸载重装. 1.0 卸载过程 打开计算机-卸载或更改软件- ...
- 【leetcode】1179. Reformat Department Table
题目如下: SQL Schema Table: Department +---------------+---------+ | Column Name | Type | +------------- ...
- CSS设置背景色
最好用background-color:rgba(37,77,113,1); 因为用opacity会让所有自标签都改变
- 谁掳走了 nginx.pid 文件?
1.重载配置 执行 nginx -s reload 命令,报错:找不到 nginx.pid 文件,无法打开.曾经屡试不爽的命令,此时,竟然失灵了? 刚开始,我一头雾水,有点丈二和尚摸不着头脑… ...
- Django 的 CBV&FBV
Django FBV, function base view 视图里使用函数处理请求 url 1 url(r‘^users/‘, views.users), views 1 2 3 4 5 from ...