说明
本文代码基于soul
2021.2.4 master分支版本。
准备
请先阅读soul
官方文档 数据同步原理,对soul
数据同步原理有个基本的了解。
如何开启HTTP同步策略
这个小节主要引自soul官网 使用不同的数据同步策略
soul-admin的配置
application.yml
中添加如下配置,或是在启动参数中添加--soul.sync.http=''
,然后重启服务:
soul:
sync:
http:
enabled: true
soul-bootstrap的配置
引入如下依赖:
<!--soul data sync start use http-->
<dependency>
<groupId>org.dromara</groupId>
<artifactId>soul-spring-boot-starter-sync-data-http</artifactId>
<version>${last.version}</version>
</dependency>
然后修改配置文件,开启HTTP
长轮询配置:
soul:
sync:
http:
url : http://localhost:9095
重启soul-bootstrap
即可。
HTTP长轮询原理
主要参考、整理官网文档 数据同步设计
首先,还是得拿出官方图说事儿.
一般客户端、服务端之间数据同步,无非推、拉两种模型。官方这张图清晰的指出HTTP
长轮询是拉模式,另外的WebSocket
和ZooKeeper
则是推模式。
HTTP
长轮询机制借鉴Apllo
、Nacos
等框架的思想,网关soul-bootstrap
会发HTTP
查询请求给soul-admin
,查询目前最新的配置,该查询请求超时时间为90s,即读取配置信息时soul-bootstrap
最多等待就是90s。而soul-admin
收到查询请求后,将会异步处理,先将其放到阻塞队列中,soul-admin
会额外创建一个60s后执行的调度任务,该任务会从阻塞队列中拿到这个请求、进行处理,保证请求肯定得到响应。另一方面,如果在60s内,管理员在soul-admin
中更改了配置,soul-admin
会挨个移除阻塞队列中的长轮询请求、响应数据,并告知哪个group
发生了变更,网关soul-bootstrap
还需要额外发起请求更新对应group
的数据。
知道上面信息后,再回过头来看官网这张图,是不是更清晰一些了?
注意:因为soul一直在迭代中,官网文档、图可能有滞后,建议一切以代码为准。
这个是思路,我们接下来分析代码,会更清晰一些。
HTTP长轮询同步流程源码分析
soul-admin源码分析
从日志分析
我个人感觉,学习开源框架源码,主要就是看官方文档、看日志输出来寻找蛛丝马迹、还有就是看初始化过程的代码。
首先,还是看日志信息:
soul-admin
启动日志中,有关HTTP
长轮询的部分:
......
2021-02-07 10:53:53.680 INFO 5484 --- [ main] o.d.s.a.l.AbstractDataChangedListener : update config cache[PLUGIN], old: null, updated: {
group='PLUGIN', md5='fad546b10dbe417e5ded73ea359eace4', lastModifyTime=1612666433680}
2021-02-07 10:53:53.694 INFO 5484 --- [ main] o.d.s.a.l.AbstractDataChangedListener : update config cache[RULE], old: null, updated: {
group='RULE', md5='d751713988987e9331980363e24189ce', lastModifyTime=1612666433694}
2021-02-07 10:53:53.704 INFO 5484 --- [ main] o.d.s.a.l.AbstractDataChangedListener : update config cache[SELECTOR], old: null, updated: {
group='SELECTOR', md5='d751713988987e9331980363e24189ce', lastModifyTime=1612666433704}
2021-02-07 10:53:53.722 INFO 5484 --- [ main] o.d.s.a.l.AbstractDataChangedListener : update config cache[META_DATA], old: null, updated: {
group='META_DATA', md5='d751713988987e9331980363e24189ce', lastModifyTime=1612666433722}
2021-02-07 10:53:53.723 INFO 5484 --- [ main] a.l.h.HttpLongPollingDataChangedListener : http sync strategy refresh interval: 300000ms
......
那我们基本就可以从上面日志基本可以确定,需要看AbstractDataChangedListener
、HttpLongPollingDataChangedListener
。看下这两个类结构、代码即可知道这里使用了模板方法
设计模式,主要流程由AbstractDataChangedListener
定义,HttpLongPollingDataChangedListener
则实现一些细节逻辑。
有关细节逻辑后面分析,我们接下来从初始化过程验证一下。
从soul-admin初始化过程分析
soul-admin
初始化类DataSyncConfiguration
中可以看到涉及HTTP
长轮询同步的代码如下:
@Configuration
public class DataSyncConfiguration {
......
@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);
}
}
......
}
可以看到只有配置了soul.sync.http.enabled
属性,才会实例化HttpLongPollingDataChangedListener
。那结合上述soul
官网中提到的长轮询机制,我们可以想见,这个HttpLongPollingDataChangedListener
就应该包含调度任务、阻塞队列、对于请求的处理等逻辑。
HttpLongPollingDataChangedListener分析
由于采用了模板方法
设计模式,我们要对HttpLongPollingDataChangedListener
分析,必须先把父类AbstractDataChangedListener
逻辑搞清。
AbstractDataChangedListener
主要代码如下(略去了结构重复的n多内容):
@Slf4j
@SuppressWarnings("all")
public abstract class AbstractDataChangedListener implements DataChangedListener, InitializingBean {
//常量缓存
protected static final ConcurrentMap<String, ConfigDataCache> CACHE = new ConcurrentHashMap<>();
//AppAuthService, PluginService, RuleService, SelectorService, MetaDataService的引用,用于读写数据库
......
//实现DataChangedListener接口,此处仅截取了selector相关,实际还有onAppAuthChanged、onPluginChanged等实现。实现思路跟下面selector这个完全一致,都是在执行完刷新对应缓存后、调用对应的afterXxxChanged方法。
@Override
public void onSelectorChanged(final List<SelectorData> changed, final DataEventTypeEnum eventType) {
if (CollectionUtils.isEmpty(changed)) {
return;
}
this.updateSelectorCache();
this.afterSelectorChanged(changed, eventType);
}
//更新selector缓存后,调用该方法。
protected void afterSelectorChanged(final List<SelectorData> changed, final DataEventTypeEnum eventType) {
}
//InitializingBean接口,属性赋值后、初始化之前执行
@Override
public final void afterPropertiesSet() {
updateAppAuthCache();
updatePluginCache();
updateRuleCache();
updateSelectorCache();
updateMetaDataCache();
afterInitialize();
}
//模板方法
protected abstract void afterInitialize();
//更新缓存
protected <T> void updateCache(final ConfigGroupEnum group, final List<T> data) {
String json = GsonUtils.getInstance().toJson(data);
ConfigDataCache newVal = new ConfigDataCache(group.name(), json, Md5Utils.md5(json), System.currentTimeMillis());
ConfigDataCache oldVal = CACHE.put(newVal.getGroup(), newVal);
log.info("update config cache[{}], old: {}, updated: {}", group, oldVal, newVal);
}
//更新selector、rule、plugin、appAuth、metadata缓存的工具方法
.......
}
可以看到AbstractDataChangedListener
主要是实现DataChangedListener
接口,同时利用模板方法
设计模式,提供了扩展的余地,在数据变化时刷新缓存、调用对应的模板方法。
之前也分析过,在soul-admin
中利用Spring
事件监听机制,收到DataChangedEvent
事件后会将其发给对应的listener
,由listener
进一步处理: