Soul网关源码学习03
soul-admin数据同步
soul-admin 使用 websocket 的方式实现数据同步,会保存与客户端的 session 信息在集合中,数据的更新以广播或单播的方式对soul-web进行通知。
DataSyncConfiguration
数据同步的方式,支持多种的配置,先看配置 websocket 的方式。
@Configuration
//使用websocket同步的开关
@ConditionalOnProperty(name = "soul.sync.websocket.enabled", havingValue = "true")
@EnableConfigurationProperties(WebsocketSyncProperties.class)
static class WebsocketListener {
//发送通知事件的实现类WebsocketDataChangedListener
@Bean
@ConditionalOnMissingBean(WebsocketDataChangedListener.class)
public DataChangedListener websocketDataChangedListener() {
return new WebsocketDataChangedListener();
//Websocket服务端
@Bean
@ConditionalOnMissingBean(WebsocketCollector.class)
public WebsocketCollector websocketCollector() {
return new WebsocketCollector();
}
//使用@ServerEndpoint注解需要实例化ServerEndpointExporter
@Bean
@ConditionalOnMissingBean(ServerEndpointExporter.class)
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}
1、soul.sync.websocket.enabled=true 使用websocket同步的开关。
2、实例化发送通知事件的实现类,实例化websocket服务端。
3、使用容器中使用@ServerEndpoint,需要单独的声明ServerEndpointExporte。
WebsocketCollector
使用注解的方式,对 websocket 请求进行处理。
@Slf4j
@ServerEndpoint("/websocket")
public class WebsocketCollector {
private static final Set<Session> SESSION_SET = new CopyOnWriteArraySet<>();
private static Session session;
//建立连接
@OnOpen
public void onOpen(final Session session) {
log.info("websocket on open successful....");
SESSION_SET.add(session);
}
//收到客户端消息
@OnMessage
public void onMessage(final String message, final Session session) {
if (message.equals(DataEventTypeEnum.MYSELF.name())) {
WebsocketCollector.session = session;
//数据同步事件
SpringBeanUtils.getInstance().getBean(SyncDataService.class).syncAll(DataEventTypeEnum.MYSELF);
}
}
//连接关闭
@OnClose
public void onClose(final Session session) {
SESSION_SET.remove(session);
WebsocketCollector.session = null;
}
//发生错误
@OnError
public void onError(final Session session, final Throwable error) {
SESSION_SET.remove(session);
WebsocketCollector.session = null;
log.error("websocket collection error: ", error);
}
//消息发送
public static void send(String message, DataEventTypeEnum type) {
if (StringUtils.isNotBlank(message)) {
if (DataEventTypeEnum.MYSELF == type) {
try {
//单个发送
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);
} catch (IOException e) {
log.error("websocket send result is exception: ", e);
}
}
}
}
}
1、使用静态变量保存 Session 并发的场景存在bug,所以我已经向 soul 提了PR,并且已经合并。
2、解决办法就是使用 ThreadLocal 保存 Session 信息,同步结束后删除,避免OOM。
DataChangedEventDispatcher
使用观察者模式对数据的更新进行事件监听。
@Component
public class DataChangedEventDispatcher implements ApplicationListener<DataChangedEvent>, InitializingBean {
private ApplicationContext applicationContext;
private List<DataChangedListener> listeners;
public DataChangedEventDispatcher(final ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
@Override
@SuppressWarnings("unchecked")
public void onApplicationEvent(final DataChangedEvent event) {
for (DataChangedListener listener : listeners) {
switch (event.getGroupKey()) {
case APP_AUTH:
listener.onAppAuthChanged((List<AppAuthData>) event.getSource(), event.getEventType());
break;
case PLUGIN:
listener.onPluginChanged((List<PluginData>) event.getSource(), event.getEventType());
break;
case RULE:
listener.onRuleChanged((List<RuleData>) event.getSource(), event.getEventType());
break;
case SELECTOR:
listener.onSelectorChanged((List<SelectorData>) event.getSource(), event.getEventType());
break;
case META_DATA:
listener.onMetaDataChanged((List<MetaData>) event.getSource(), event.getEventType());
break;
default:
throw new IllegalStateException("Unexpected value: " + event.getGroupKey());
}
}
}
@Override
public void afterPropertiesSet() {
Collection<DataChangedListener> listenerBeans = applicationContext.getBeansOfType(DataChangedListener.class).values();
this.listeners = Collections.unmodifiableList(new ArrayList<>(listenerBeans));
}
}
1、实现InitializingBean接口,获得所有接口的实现类,说明soul支持多协议的通知。
2、监听到DataChangedEvent事件,循环所有协议根据事件类型进行通知。