上文回顾
Nacos客户端在启动的时候,初始化了一个长轮询任务,检查服务端的数据是否发生变更,如果没有发生变更,则服务端会挂起请求,29.5秒后比对数据将结果返回,如果发生变更,则马上对比数据,将变更的groupKey返回,客户端拿到groupKey后再去服务端获取最新值。将最新值设置到cacheData中拿到cacheDate的监听器,通知configServer数据发生变更。这就是客户端的实现过程。
服务端
接下来重点分析服务端如何实现,并且带着几个问题去寻找答案。
-
客户端请求超时时间为什么要设置30s
-
为什么配置更新客户端马上会得到响应
首先我们从客户端发送长轮询请求这个方法着手,发送post请求的代码入下
HttpRestResult<String> result = agent
.httpPost(Constants.CONFIG_CONTROLLER_PATH + "/listener",
headers, params, agent.getEncode(),readTimeoutMs);
可知请求路径为 /v1/cs/configs/listener, 在服务端找到对应的controller方法,如下图,有一点需要注意的是,这个方法没有返回值,所以只能通过response向客户端写数据。
进入doPollingConfig方法,判断如果是长轮询则走长轮询方法,否则走短轮询方法,重点关注长轮询
进入到 addLongPollingClient 方法中
重点看下ClientLongPolling这个类,一个是属性,另一个是run方法
上面这部分代码主要功能是将客户端的请求封装成一个长轮询任务,将任务放到延时队列中,延时时间29.5秒,到期后从队列中取出任务执行,执行的逻辑为比对客户端和服务端MD5值,将变化的groupKey返回给客户端。这里有一个对象值得注意,那就是 allSubs ,allSubs 也存储了该长轮询任务,就是为了当数据变更的时候,能够从这个allSubs中拿到对应的任务,进而拿到里面的asyncTimeoutFuture和asyncContext,将响应提前返回,同时取消延时队列中的这个任务。
配置变更
当服务端接收到变更配置的请求时,服务端会做什么呢?我们在控制台修改数据定位到请求路径是 /nacos/v1/cs/configs 请求方式为post ,这样就定位到服务端的相关controller,com.alibaba.nacos.config.server.controller.ConfigController#publishConfig,跟进这个方法看看做了什么。
这个方法很长,获取请求参数和校验请求参数就没有截进来,只截取重要的部分,从代码可知,数据变更的操作是先保存再通知
看下通知方法:ConfigChangePublisher
.notifyConfigChange(new ConfigDataChangeEvent(false, dataId, group, tenant, time.getTime()));主要是创建一个 改变事件对象,将这个对象通过NotifyCenter发布出去。
这个方法较深,一步一步跟进去,发现最终调用了com.alibaba.nacos.common.notify.DefaultPublisher#publish
可以看到最终将事件放到了阻塞队列中,这应该是生产者消费者模式,继续寻找queue对象在哪里取出事件消费
首先在NotifyCenter类中有一个静态代码块,会初始化DefaultPublisher这个对象,而DefaultPublisher对象实现了Thread,表明他是一个线程对象,在其内部重写的run方法中,我们可以看到queue.take方法,入下
receiveEvent方法是遍历所有订阅者,忽略掉过期事件,通知订阅者该事件
进入notifySubscriber方法,发现创建了一个任务,然后执行该事件
回调到注册订阅者的地方
执行任务是创建一个DataChangeTask对象,然后从allSubs中找到与这个对象的groupKey相同的任务,先将这个任务从allSubs中移除,再将这个groupKey返回给客户端。
最后看下clientSub.sendResponse(Arrays.asList(groupKey));这个方法做了什么
至此,服务端处理逻辑就完成了
总结:
-
接收到客户端的请求,将请求封装成clientLongPolling任务提交到延时调度线程池,线程池延时时间为29.5s,同时将这个clientLongPolling任务添加到一个allSubs集合中
-
在29.5秒这段期间内,没有数据变更,则29.5s后开始执行clientLongPolling任务,首先将clientLongPolling任务从allSubs中移除,再检查客户端的MD5和服务端的MD5值是否一致,将不一致的返回。
-
29.5s内服务端接收到数据更新操作,发布一个异步通知,就是将这个请求添加到一个阻塞队列,有一个线程从这个队列中取数据消费,消费逻辑是比对客户端和服务端的MD5是否一致,然后将比对结果返回,最后从allSub中取出相对应的clientLongPolling任务,将这个任务取消,这样延时队列到期将不会做任何操作。