概述
前两篇文章以websocket同步方式介绍了整个数据同步的流程,今天我们来看看http长轮询同步数据是如何实现的。使用websocket同步数据时,在soul-admin服务端有负责监听数据变化事件的HttpLongPollingDataChangedListener
以及负责接收soul网关连接的WebsocketCollector
,在soul服务端有实现SyncDataService
的WebsocketSyncDataService
,负责实现当接收到配置变化。下面我们来对应看看http长轮询这几部分是如何实现的。
网关端
在soul网关端如果要开启http同步数据的方式需要做如下配置:
1 | soul: |
与websocket类似,他需要配置admin的地址,不过比较奇怪的是他并不需要配路径。通过HttpSyncDataConfiguration
我们发现soul网关发现soul.sync.http.url
参数存在时会向spring容器中注册了HttpSyncDataService
,构造Service需要
soul.sync.http
下的配置,需要PluginData、MetaData、AuthData这些订阅者,总体上和websocket类似。下面我们详细看下HttpSyncDataService
的实现:
1 | public class HttpSyncDataService implements SyncDataService, AutoCloseable { |
整体代码逻辑是其实比较明显,soul网关启动时请求每个soul-admin的/configs/fetch
接口获取全量配置信息。同时会启动soul-admin数量的线程调用admin的/configs/listener
来校验配置是否变更,如果变更再具体调用/configs/fetch
来全量更新数据。在本类中所有请求都是借助RestTemplate发送,设置了readTimeOut是90秒,而线程轮询的时间间隔其实是30秒,这样就应该达到了TCP连接不断的效果,也就是所谓的长轮询。
admin端
soul-admin中没有websocket的Collector,因为http长轮询访问的接口是都是http的所有都在org.dromara.soul.admin.controller
下。那么我们看看HttpLongPollingDataChangedListener
的实现。这个类不是直接实现DataChangedListener
,而是继承抽象类AbstractDataChangedListener
。首先来看AbstractDataChangedListener
,代码如下:
1 | public abstract class AbstractDataChangedListener implements DataChangedListener, InitializingBean { |
上述是pluginData相关的实现,其他数据类似,之前我们聊到过有PluginData更新时间发生时,会调用onPluginChanged
,在这里面主要是先更新本类内存中的配置,然后再调用子类实现是的afterPluginChanged
。同时在相关实现类构造时,调用了afterPropertiesSet
它主要是先把所有配置加载到内存中,然后调用子类的 afterInitialize
方法。 下面我们看看http长轮询继承这个抽象类又做了哪些逻辑。代码如下:
1 | public class HttpLongPollingDataChangedListener extends AbstractDataChangedListener { |
这块比较复杂,但是要把握住核心思想,即当soul网关请求 /configs/listener 时需要实现的逻辑是:
- 如果配置有变则立马返回
- 如果配置在60秒内有边也立马返回
- 如果60秒内配置无更新则也返回
那么是怎么实现的呢?是有3个线程在scheduler线程池里互相配合的结果。首先,如果请求发过来第一遍验证发现配置有改变则直接返回。如果没有更新则创建一个LongPollingClient任务,该任务有创建了1个60秒后执行回写响应的任务asyncTimeoutFuture,LongPollingClient任务放在clients这个变量里,asyncTimeoutFuture任务保存在LongPollingClient中。如果60秒内都没有配置更新则asyncTimeoutFuture会执行回写响应。如果60秒内有配置更新则会开启DataChangeTask任务,他会从clients拿到所有的LongPollingClient任务,依次执行取消asyncTimeoutFuture任务,然后回写response。
总结
soul的http长轮询设计的十分巧妙,值得深入学习。总得来说长轮询的机制时应对频繁查询数据是否变更的场景,实现方式是首先是http的read的超时必须够长,然后是http服务端要阻塞请求,但阻塞的时间要短于read超时时间。服务端只有在有数据变更或超时时才返回数据。这样做即可以保证数据更新立马被请求端感知,又能减少TCP连接。实现时注意要使用AsyncContext
将请求移交给别的线程,不要让Servlet阻塞。