JetLinks物聯網基礎平臺-事件驅動

事件驅動

在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訂閱消息.

  1. 實現MessageConnector接口.
  2. 將連接器中的連接實現MessageConnection接口.
  3. 根據情況,如果連接需要訂閱消息,則還要實現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;
        }
    }

}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章