soul网关源码学习06-websocket数据同步
目标:
- 演示websocket方式数据同步
- 梳理websocket数据同步流程
一、数据同步分析
下图展示了 Soul 数据同步的流程,Soul 网关在启动时,会从从配置服务同步配置数据,并且支持推拉模式获取配置变更信息,并且更新本地缓存。而管理员在管理后台,变更用户、规则、插件、流量配置,通过推拉模式将变更信息同步给 Soul 网关,具体是 push
模式,还是 pull
模式取决于配置。关于配置同步模块,其实是一个简版的配置中心。
如下图所示,soul-admin
在用户发生配置变更之后,会通过 EventPublisher
发出配置变更通知,由 EventDispatcher
处理该变更通知,然后根据配置的同步策略(http、weboscket、zookeeper),将配置发送给对应的事件处理器。
如果是 websocket 同步策略,则将变更后的数据主动推送给 soul-web
,并且在网关层,会有对应的 WebsocketCacheHandler
处理器处理来处 admin 的数据推送。
将网关与 admin 建立好 websocket 连接时,admin 会推送一次全量数据,后续如果配置数据发生变更,则将增量数据通过 websocket 主动推送给 soul-web
使用websocket同步的时候,特别要注意断线重连,也叫保持心跳。soul使用java-websocket 这个第三方库来进行websocket连接。
二、启动项目
了解了websocket原理之后,开始进入soul数据同步的学习。soul-admin使用websocket方式进行数据同步,这里打开websocket开关。
soul:
sync:
websocket:
enabled: true
启动soul-admin
、soul-bootstrap
、soul-examples-http
,打开系统:http://localhost:9095,admin/123456
规则/http/order/findById
处于开启状态,发起一次http请求,接口正常返回。
修改规则/http/order/findById
为关闭状态。
再发起一次http请求,接口返回Rule not found!
从上面的演示可以看出,管理后台的配置会自动同步到网关,这个同步是怎么样实现的呢?
从websocket原理可以看出,首先要找到客户端和服务端,两边建立连接,然后可以互相通信。
三、数据流向分析
admin
启动,websocket
服务端暴露端点等待连接。bootstrap
启动的时候会建立与服务端的连接并发送消息。admin
收到消息之后发送全量的数据到bootstrap
客户端,客户端更新全量数据。admin
做CRUD
操作时,会发送增量数据到bootstrap
客户端,客户端更新增量数据。
四、数据同步流程
- 启动
admin
,加载事件分发器DataChangedEventDispatcher
- 实例化
DataChangedListener
的实现类WebsocketDataChangedListener
,开始监听admin
数据的变化。 - 根据配置加载
websocket
服务端WebsocketCollector
- 启动
bootstrap
,加载websocket
数据同步类WebsocketSyncDataService
- 初始化
websocket
客户端SoulWebsocketClient
,然后会自动发送一条消息到服务端要全量同步一次数据。 - 客户端接收到消息后把数据发回给客户端,客户端把数据更新到缓存中。
admin
和bootstrap
都启动之后,修改admin
的数据,会触发事件监听,服务端会把最新有变动的数据发送到客户端,客户端把数据更新到缓存中。
五、源码分析
首先,找一下服务端,在soul-admin
找到数据同步配置类DataSyncConfiguration
,然后找到WebsocketListener
,打上断点。
@Bean
@ConditionalOnMissingBean(WebsocketDataChangedListener.class)
public DataChangedListener websocketDataChangedListener() {
return new WebsocketDataChangedListener();
}
重启soul-admin,debug会进入到上面的方法中,然后查看调用栈,找到DataChangedEventDispatcher
的afterPropertiesSet
方法。
public void afterPropertiesSet() {
Collection<DataChangedListener> listenerBeans = applicationContext.getBeansOfType(DataChangedListener.class).values();
this.listeners = Collections.unmodifiableList(new ArrayList<>(listenerBeans));
}
这个applicationContext.getBeansOfType(DataChangedListener.class)
类似于工厂模式和策略模式的结合,加载所有实现了DataChangedListener
接口的子类,这样做的好处是易于扩展。
但是从debug过程可以看到,这里只加载了WebsocketDataChangedListener
,因为这里只配置了websocket一种同步方式,注意看DataSyncConfiguration
这个配置类里面其他方式的@ConditionalOnProperty
注解。
public void onApplicationEvent(final DataChangedEvent event) {
for (DataChangedListener listener : listeners) {
//省略
}
}
接着看一下WebsocketDataChangedListener
,里面就是各种onchange,监听数据的变化,这里看一下onPluginChanged
,其他的类似。定义一个统一的WebsocketData
,然后通过WebsocketCollector
的send
方法发送数据。
public void onPluginChanged(final List<PluginData> pluginDataList, final DataEventTypeEnum eventType) {
WebsocketData<PluginData> websocketData =
new WebsocketData<>(ConfigGroupEnum.PLUGIN.name(), eventType.name(), pluginDataList);
WebsocketCollector.send(GsonUtils.getInstance().toJson(websocketData), eventType);
}
public static void send(final String message, final DataEventTypeEnum type) {
if (StringUtils.isNotBlank(message)) {
if (DataEventTypeEnum.MYSELF == type) {
try {
Session session = (Session) ThreadLocalUtil.get(SESSION_KEY);
if (session != null) {
session.getBasicRemote().sendText(message);
}
} catch (IOException e) {
log.error("websocket send result is exception: ", e);
}
return;
}
for (Session session : SESSION_SET) {
try {
session.getBasicRemote().sendText(message