事件驅動
在jetlinks中大量使用到事件驅動,在之前,我們是使用spring event
作爲事件總線進行進程內事件通知的.
由於spring event
不支持響應式,所以使用消息網關(MessageGateway)
來替代spring event
.
消息網關
有2個作用,1. 事件驅動 2. 設備消息統一管理.
概念
在消息網關中分爲: 消息網關(MessageGateway)
,消息連接器(MessageConnector)
,消息連接(MessageConnection)
,
消息訂閱器(MessageSubscriber)
,消息發佈器(MessagePublihser)
.
網關中對消息使用topic
進行區分,而不是像spring event
那樣使用java類型來區分.
Topic
採用樹結構來定義topic如:/device/id/message/type
.
topic支持路徑通配符,如:/device/**
或者/device/*/message/*
.
TIP
**
表示匹配多層路徑,*
表示匹配單層路徑.不支持
前後匹配,如:/device/id-*/message
消息網關
消息網關
從連接器
中訂閱消息連接
,當有連接創建
時,會根據連接類型進行不同的操作.
當消息連接
是一個訂閱器時(isSubscriber
)時,會從MessageSubscriber
中接收消息訂閱請求(onSubscribe)
,
並管理每一個連接的訂閱信息.當一個消息連接
是一個發佈器時(isPublisher
),會從MessagePublihser
中訂閱消息(onMessage)
,
當發佈器發送了消息(TopicMessage)
時,網關會根據消息的topic
獲取訂閱了此topic的消息連接
,並將消息推送給對應的訂閱器
.
使用
訂閱消息:
@Subscribe("/device/**")
public Mono<Void> handleDeviceMessage(DeviceMessage message){
return publishDeviecMessageToKafka(message);
}
發佈消息:
@Autowired
private MessageGateway gateway;
public Mono<Void> saveUser(UserEntity entity){
return service.saveUser(entity)
.then(gateway.publish("/user/"+entity.getId()+"/saved",entity))
.then();
}
自定義連接器
消息網關還可以用於消息轉發,如實現設備消息統一網關.如: 通過CoAP
發送消息,使用MQTT
訂閱消息.
- 實現
MessageConnector
接口. - 將連接器中的連接實現
MessageConnection
接口. - 根據情況,如果連接需要訂閱消息,則還要實現
MessageSubscriber
,如果需要發佈消息則實現MessagePublisher
.
例子:
public class MqttMessageConnector implements MessageConnector {
private MqttServer mqttServer;
private ClientAuthenticator authenticator;
private int maxClientSize;
@Nonnull
@Override
public String getId() {
return mqttServer.getId();
}
@Nonnull
@Override
public Flux<MessageConnection> onConnection() {
//從MQTT服務中訂閱mqtt連接
return mqttServer
.handleConnection()
.filter(conn -> {
if (conn.getAuth().isPresent()) {
return true;
}
conn.reject(MqttConnectReturnCode.CONNECTION_REFUSED_NOT_AUTHORIZED);
return false;
})
.flatMap(conn -> {
MqttAuth auth = conn.getAuth().orElse(null);
if (auth == null) {
return Mono.empty();
}
//認證
return authenticator
.authorize(new MqttAuthenticationRequest(conn.getClientId(), auth.getUsername(), auth.getUsername(), DefaultTransport.MQTT))
.map(resp -> new MqttMessageConnection(conn.accept(), resp));
});
}
@AllArgsConstructor
class MqttMessageConnection implements
MessageConnection,
MessagePublisher,
MessageSubscriber {
private MqttConnection mqttConnection;
private ClientAuthentication authentication;
@Override
public String getId() {
return mqttConnection.getClientId();
}
@Override
public void onDisconnect(Runnable disconnectListener) {
mqttConnection.onClose(conn -> disconnectListener.run());
}
@Override
public void disconnect() {
mqttConnection.close().subscribe();
}
@Override
public boolean isAlive() {
return mqttConnection.isAlive();
}
@Nonnull
@Override
public Flux<TopicMessage> onMessage() {
//從MQTT連接中訂閱消息
return mqttConnection
.handleMessage()
.flatMap(publishing -> {
MqttMessage mqttMessage = publishing.getMessage();
String topic = mqttMessage.getTopic();
return authentication
.getAuthority(topic)
.filter(auth -> auth.has(TopicAuthority.PUB))
.map(auth -> TopicMessage.of(mqttMessage.getTopic(), mqttMessage))
.switchIfEmpty(Mono.fromRunnable(() -> log.warn("客戶端[{}]推送無權限topic[{}]消息", getId(), topic)))
.doOnEach(ReactiveLogger.onError(err -> {
log.error("處理MQTT消息失敗", err);
}))
;
}).onErrorContinue((err, obj) -> {
});
}
@Nonnull
@Override
public Mono<Void> publish(@Nonnull TopicMessage message) {
//將消息推送給MQTT
return mqttConnection.publish(SimpleMqttMessage.builder()
.payload(message.getMessage().getPayload())
.topic(message.getTopic())
.qosLevel(0)
.build());
}
@Nonnull
@Override
public Flux<Subscription> onSubscribe() {
//MQTT客戶端訂閱topic
return mqttConnection
.handleSubscribe(true)
.flatMapIterable(sub -> sub.getMessage().topicSubscriptions())
.map(MqttTopicSubscription::topicName)
.filterWhen(topic -> authentication.getAuthority(topic).map(auth -> auth.has(TopicAuthority.SUB)))
.map(Subscription::new);
}
@Nonnull
@Override
public Flux<Subscription> onUnSubscribe() {
//MQTT客戶端取消訂閱
return mqttConnection
.handleUnSubscribe(true)
.flatMapIterable(msg -> msg.getMessage().topics())
.map(Subscription::new);
}
@Override
public boolean isShareCluster() {
//Pro將支持共享集羣的消息,如: 節點1的網關收到了消息,MQTT從服務節點2訂閱了請求.
return false;
}
}
}