soul源码解读(七)
数据同步之http长轮询-admin端
官网介绍
zookeeper、websocket 数据同步的机制比较简单,而 http 同步会相对复杂一些。Soul 借鉴了 Apollo、Nacos 的设计思想,取其精华,自己实现了 http 长轮询数据同步功能。注意,这里并非传统的 ajax 长轮询!
下面是 soul 官网上的图
http 长轮询机制如上所示,soul-web 网关请求 admin 的配置服务,读取超时时间为 90s,意味着网关层请求配置服务最多会等待 90s,这样便于 admin 配置服务及时响应变更数据,从而实现准实时推送。
http 请求到达 sou-admin 之后,并非立马响应数据,而是利用 Servlet3.0 的异步机制,异步响应数据。首先,将长轮询请求任务 LongPollingClient 扔到 BlocingQueue 中,并且开启调度任务,60s 后执行,这样做的目的是 60s 后将该长轮询请求移除队列,即便是这段时间内没有发生配置数据变更。因为即便是没有配置变更,也得让网关知道,总不能让其干等吧,而且网关请求配置服务时,也有 90s 的超时时间。
启动服务
我们启动服务,来看下这套机制是怎么运行的。
1.修改 admin 同步配置,启动服务
sync:
http:
enabled: true
2.修改 bootstrap 同步配置
sync:
http:
url : http://localhost:9095
3.然后看下 pom 文件里有没有 http 同步的 jar,然后启动 bootstrap 服务
<!--soul data sync start use http-->
<dependency>
<groupId>org.dromara</groupId>
<artifactId>soul-spring-boot-starter-sync-data-http</artifactId>
<version>${project.version}</version>
</dependency>
<!-- soul sign plugin start-->
我们可以在 admin 启动日志里看到这么一句
2021-01-21 23:33:33.296 INFO 18328 --- [ main] a.l.h.HttpLongPollingDataChangedListener : http sync strategy refresh interval: 300000ms
然后找到这句日志打印的地方
// HttpLongPollingDataChangedListener.java
protected void afterInitialize() {
long syncInterval = httpSyncProperties.getRefreshInterval().toMillis();
// Periodically check the data for changes and update the cache
// 设置一个延迟执行的线程池,刷新本地缓存
scheduler.scheduleWithFixedDelay(() -> {
log.info("http sync strategy refresh config start.");
try {
this.refreshLocalCache();
log.info("http sync strategy refresh config success.");
} catch (Exception e) {
log.error("http sync strategy refresh config error!", e);
}
}, syncInterval, syncInterval, TimeUnit.MILLISECONDS);
log.info("http sync strategy refresh interval: {}ms", syncInterval);
}
然后我们找到了一个非常关键的类 HttpLongPollingDataChangedListener ,看名字就知道这个类应该就是 http 长轮询的类。
在我写文章的时候,看到控制台又打了日志
2021-01-21 23:38:33.296 INFO 18328 --- [-long-polling-2] a.l.h.HttpLongPollingDataChangedListener : http sync strategy refresh config start.
...
2021-01-21 23:38:33.476 INFO 18328 --- [-long-polling-2] a.l.h.HttpLongPollingDataChangedListener : http sync strategy refresh config success.
很明显可以看出,这段日志和上面的日志相差5分钟,说明 admin 会每隔5分钟从数据库刷新一次配置。正好对应了上面那段代码。
然后我们往上找,看下 afterInitialize() 是重写了 AbstractDataChangedListener 里的方法,afterPropertiesSet()里面调用了 afterInitialize() ,AbstractDataChangedListener 又实现了 InitializingBean。
它们的继承关系如下:
然后我们继续找下 HttpLongPollingDataChangedListener 是什么被注册的。
关键代码如下:
// DataSyncConfiguration.java
@Configuration
@ConditionalOnProperty(name = "soul.sync.http.enabled", havingValue = "true")
@EnableConfigurationProperties(HttpSyncProperties.class)
static class HttpLongPollingListener {
@Bean
@ConditionalOnMissingBean(HttpLongPollingDataChangedListener.class)
public HttpLongPollingDataChangedListener httpLongPollingDataChangedListener(final HttpSyncProperties httpSyncProperties) {
return new HttpLongPollingDataChangedListener(httpSyncProperties);
}
}
程序在启动的时候,会先去加载 DataSyncConfiguration (这个类配置了 @Configuration),然后会执行上面的代码,判断 soul.sync.http.enabled = true 的话,就初始化一个 HttpLongPollingDataChangedListener ,HttpLongPollingDataChangedListener 初始化完成之后,就会调用上面的 afterInitialize() 。
4.在 admin 后台更新一个插件的状态,然后 debug 跟踪下调用链。
admin 端同步数据流程
// HttpLongPollingDataChangedListener.java
protected void afterPluginChanged(final List<PluginData> changed, final DataEventTypeEnum eventType) {
scheduler.execute(new DataChangeTask(ConfigGroupEnum.PLUGIN));
}
接着我们看下 DataChangeTask的 run()
public void run() {
for (Iterator<LongPollingClient> iter = clients.iterator(); iter.hasNext();) {
//其他代码省略
client.sendResponse(Collections.singletonList(groupKey));
}
}
LongPollingClient 实现了 runable ,我们继续看里面的 run()
public void run() {
this.asyncTimeoutFuture = scheduler.schedule(() -> {
clients.remove(LongPollingClient.this);
List<ConfigGroupEnum> changedGroups = compareChangedGroup((HttpServletRequest) asyncContext.getRequest());
sendResponse(changedGroups);
}, timeoutTime, TimeUnit.MILLISECONDS);
clients.add(this);
}
可以看到上面也是创建一个延迟线程池去获取配置数据
我们接着往下看compareChangedGroup()
private List<ConfigGroupEnum> compareChangedGroup(final HttpServletRequest request) {
//其他代码省略
if (this.checkCacheDelayAndUpdate(serverCache, clientMd5, clientModifyTime)) {
//如果数据需要更新,就返回更新数据
changedGroup.add(group);
}
}
return changedGroup;
}
接下来 checkCacheDelayAndUpdate 是重点,我们下一篇接着分析。
总结
今天主要了解了下官网介绍的 http 长轮询机制
然后自己启动服务,了解了 admin 端发送消息的流程。