引言:在上文分析中客户端会一直轮询阻塞队列【listenExecutebell】去比较客户端和服务端配置内容md5是否一致,不一致则通知注册的listener完成回调,当阻塞队列里有元素时会立即执行,没有元素时会等待5秒执行。那都在什么时候往队列中添加元素从而触发执行呢
一、内容提要
阻塞队列添加时机
客户端添加Listener时添加
客户端删除Listener时添加
服务端通知内容变更时添加
建立gRPC连接时添加
服务端变更发布流程
通过配置中心发起变更请求
配置变更内容被写入数据库
向本节点连接的Client发送变更通知
向集群中的其他节点发送变更通知
向Client发送变更通知
首先构建DumpTask
处理DumpTask时发布ConfigDumpEvent事件
处理ConfigDumpEvent时发布LocalDataChangeEvent事件
处理LocalDataChangeEvent事件:@1 先从缓存中拿出注册Client列表 @2 根据client从缓存获取gRPC连接 @3 构建ConfigChangeNotifyRequest @4 通过gRPC向客户端发送变更通知
客户端处理变更通知,客户端接到请求后向阻塞队列添加new Object元素
向其他节点发送变更通知
通过gRPC向集群中其他节点发送变更通知
集群节点收到变更通知后再下发给连接自己的Client
二、死循环回顾
public void startInternal() throws NacosException {
executor.schedule(new Runnable() {
@Override
public void run() {
while (true) { // 一直运行
try {
// 最长等待5秒
listenExecutebell.poll(5L, TimeUnit.SECONDS);
executeConfigListen();
} catch (Exception e) {
LOGGER.error("[ rpc listen execute ] [rpc listen] exception", e);
}
}
}
}, 0L, TimeUnit.MILLISECONDS);
}
队列添加元素
@Override
public void notifyListenConfig() {
listenExecutebell.offer(bellItem);
}
备注: 配置内容变更机制是通过长轮询实现的。
三、阻塞队列添加时机
客户端添加Listener时添加
public void addListeners(String dataId, String group, List<? extends Listener> listeners) {
// ...
// 通过队列中增加一个对象,长轮询立即执行
agent.notifyListenConfig();
}
}
客户端删除Listener时添加
public void removeListener(String dataId, String group, Listener listener) {
group = null2defaultGroup(group);
CacheData cache = getCache(dataId, group);
if (null != cache) {
synchronized (cache) {
cache.removeListener(listener);
if (cache.getListeners().isEmpty()) {
cache.setSyncWithServer(false);
agent.removeCache(dataId, group);
}
}
}
}
public void removeCache(String dataId, String group) {
// Notify to rpc un listen ,and remove cache if success.
notifyListenConfig();
}
服务端通知内容变更时添加
在与服务端建立gRPC通道时会添加Handler用于处理服务端推送的请求,收到推送请求后向阻塞队列添加元素。
private void initRpcClientHandler(final RpcClient rpcClientInner) {
/*
* Register Config Change /Config ReSync Handler
*/
rpcClientInner.registerServerRequestHandler((request) -> {
if (request instanceof ConfigChangeNotifyRequest) {
ConfigChangeNotifyRequest configChangeNotifyRequest = (ConfigChangeNotifyRequest) request;
LOGGER.info("[{}] [server-push] config changed. dataId={}, group={},tenant={}",
rpcClientInner.getName(), configChangeNotifyRequest.getDataId(),
configChangeNotifyRequest.getGroup(), configChangeNotifyRequest.getTenant());
String groupKey = GroupKey
.getKeyTenant(configChangeNotifyRequest.getDataId(), configChangeNotifyRequest.getGroup(),
configChangeNotifyRequest.getTenant());
CacheData cacheData = cacheMap.get().get(groupKey);
if (cacheData != null) {
cacheData.setSyncWithServer(false);
notifyListenConfig(); // 向阻塞队列中添加元素
}
return new ConfigChangeNotifyResponse();
}
return null;
});
// ....
}
建立gRPC连接时添加
在建立gRPC连接时会向阻塞队列中添加元素。
rpcClientInner.registerConnectionListener(new ConnectionEventListener() {
@Override
public void onConnected() {
LOGGER.info("[{}] Connected,notify listen context...", rpcClientInner.getName());
notifyListenConfig(); // 向队列中添加元素
}
@Override
public void onDisConnect() {
String taskId = rpcClientInner.getLabels().get("taskId");
LOGGER.info("[{}] DisConnected,clear listen context...", rpcClientInner.getName());
Collection<CacheData> values = cacheMap.get().values();
for (CacheData cacheData : values) {
if (StringUtils.isNotBlank(taskId)) {
if (Integer.valueOf(taskId).equals(cacheData.getTaskId())) {
cacheData.setSyncWithServer(false);
}
} else {
cacheData.setSyncWithServer(false);
}
}
}
备注: 阻塞队列添加元素的四种情况,添加后长轮询立即执行配置内容校验。
四、服务端启动拉取配置
4.1 大体流程:
![](https://img-blog.csdnimg.cn/img_convert/5ec18ae67f3768c7503727d3071a9af7.png)
![](https://img-blog.csdnimg.cn/img_convert/9f36988ed4922be026ba599be2a5af1e.png)
4.1.1 初始化方法
1、服务端启动时就会依赖DumpService的init方法,从数据库中load 配置存储在本地磁盘上,并将一些重要的元信息例如MD5值缓存在内存中。服务端会根据心跳文件中保存的最后一次心跳时间,来判断到底是从数据库dump全量配置数据还是部分增量配置数据(如果机器上次心跳间隔是6h以内的话)。
2、全量dump当然先清空磁盘缓存,然后根据主键ID每次捞取一千条配置刷进磁盘和内存。增量dump就是捞取最近六小时的新增配置(包括更新的和删除的),先按照这批数据刷新一遍内存和文件,再根据内存里所有的数据全量去比对一遍数据库,如果有改变的再同步一次,相比于全量dump的话会减少一定的数据库I/O和磁盘I/0次数。
![](https://img-blog.csdnimg.cn/img_convert/06da08125bed2fa2749690a388643a42.png)
构建bean时候一定初始化@PostConstruct 对应的方法
![](https://img-blog.csdnimg.cn/img_convert/f7e42e8866132aeda4981bf08bc06732.png)
4.1.2 判断全量和增量获取数据
![](https://img-blog.csdnimg.cn/img_convert/f8e4e087da34a5b34ed6f1e53a0cbfed.png)
![](https://img-blog.csdnimg.cn/img_convert/e26dd78613c1038dcea31d149deaa995.png)
4.2 全量拉取
![](https://img-blog.csdnimg.cn/img_convert/88a4fcf275ef6da7b9b03a7694dd3a82.png)
![](https://img-blog.csdnimg.cn/img_convert/071a84201be929ed073cb1d37001d1fc.png)
![](https://img-blog.csdnimg.cn/img_convert/467cbfe0fe8d1a4cce1363dbc21e2fa8.png)
![](https://img-blog.csdnimg.cn/img_convert/4e32846af164940c26d00dfc437221fc.png)
总结:这里的md5值的我们学习,我们md5算法获取对应文件的值,如果这个值变化说明文件发生了变化,利用这种式我们可以通过文件md5值来判断其他是否有变化。
五、服务端变更发布流程
大体流程
![](https://img-blog.csdnimg.cn/img_convert/8c3d604cba9df83a5d9015050e9cf966.png)
发布配置变更请求
服务端的发布内容变更,通过变更请求跟踪下,后端处理请求通过ConfigController#publishConfig()实现。
![](https://img-blog.csdnimg.cn/img_convert/b18f3d4deccf5d00cb1754a7165c105f.png)
处理配置变更请求
将变更配置更新到数据库
发布ConfigDataChangeEvent事件
![](https://img-blog.csdnimg.cn/img_convert/34617a1c0c8ba13052a4be504eba1165.png)
订阅ConfigDataChangeEvent
翻到订阅该事件的处理逻辑,看看其干了什么事情,AsyncNotifyService构造函数中的onEvent()方法。从代码中可以看出当收到ConfigDataChangeEvent时,逻辑如下:
获取集群中所有节点列表
每个节点封装NotifySingleRpcTask
异步向各个节点通知配置信息变更
![](https://img-blog.csdnimg.cn/img_convert/14b53e04a7b4f103ea15012b6a7216e7.png)
4.NotifySingleRpcTask逻辑
在代码AsyncNotifyService#AsyncRpcTask部分查看逻辑框架,任务运行主要干了两件事:
向连接本节点的Client发送配置变更通知
向集群中的其他节点发送变更通知
![](https://img-blog.csdnimg.cn/img_convert/03072c6b340adfba63ac8c2d768571c0.png)
小结: 当我们从Nacos配置中心界面提交变更配置时:
1.入库;
2.向本节点连接的Client发送变更通知;
3.向集群中的其他节点发送变更通知。
六、向Client发送变更通知
6.1 首先构建DumpTask
代码坐标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));
}
6.2 处理DumpTask时发布ConfigDumpEvent事件
代码坐标DumpProcessor#process()
![](https://img-blog.csdnimg.cn/img_convert/adebd83bb06bac2146d86edc742b2f73.png)
6.3.处理ConfigDumpEvent时发布LocalDataChangeEvent事件
![](https://img-blog.csdnimg.cn/img_convert/27c88bf2bf3659c2f9af54d26d2dd511.png)
![](https://img-blog.csdnimg.cn/img_convert/10109c9c0f819ec8f9f0085313472ca0.png)
6.4 处理LocalDataChangeEvent事件
关注RpcConfigChangeNotife#onEvent事件
@Override
public void onEvent(LocalDataChangeEvent event) {
String groupKey = event.groupKey;
boolean isBeta = event.isBeta;
List<String> betaIps = event.betaIps;
String[] strings = GroupKey.parseKey(groupKey);
String dataId = strings[0];
String group = strings[1];
String tenant = strings.length > 2 ? strings[2] : "";
String tag = event.tag;
configDataChanged(groupKey, dataId, group, tenant, isBeta, betaIps, tag);
}
例如:注册在缓存的client=1627734181270_127.0.0.1_53522
![](https://img-blog.csdnimg.cn/img_convert/43fedc3353c593b08654b45d4dfe42fa.jpeg)
![](https://img-blog.csdnimg.cn/img_convert/667ec4005f858c27b70450ec6a650344.png)
6.5 客户端变更通知
![](https://img-blog.csdnimg.cn/img_convert/c911f276c4076df7e8a17a719ca13fd2.png)
![](https://img-blog.csdnimg.cn/img_convert/5c5d37c480da14eb752c05e4c184f2db.png)
七、向其他节点发送变更通知
1.接上面「服务端变更发布流程」最后步骤「向集群中的其他节点发送变更通知」
configClusterRpcClientProxy
.syncConfigChange(member, syncRequest, new AsyncRpcNotifyCallBack(task));
从缓存中获取与其他节点的rpcClient连接,key=Cluster-1.2.3.4:1111
public void asyncRequest(Member member, Request request, RequestCallBack callBack) throws NacosException {
RpcClient client = RpcClientFactory.getClient(memberClientKey(member));
if (client != null) {
client.asyncRequest(request, callBack);
} else {
throw new NacosException(CLIENT_INVALID_PARAM, "No rpc client related to member: " + member);
}
}
2.其他节点处理请求入口
GrpcRequestAcceptor#request只列出部分代码
// 执行RequestHandler逻辑
Response response = requestHandler.handleRequest(request, requestMeta);
3.其他节点由ConfigChangeClusterSyncRequestHandler处理ConfigChangeClusterSyncRequest
即:完成向客户端发送变更通知
![](https://img-blog.csdnimg.cn/img_convert/3575c73ecd8296f974706c39ccb95e29.png)
小结: 当集群其他节点收到变更通知后,会向连接到本节点的Client发送变更通知,逻辑同上一小结「向Client发送变更通知」,从而完成全部向所有Client下发变更通知。