Spring Cloud Stream應用與自定義RocketMQ Binder:實現RocketMQ繫結器

aoho發表於2018-07-01

前言: 本文作者張天,節選自筆者與其合著的《Spring Cloud微服務架構進階》,即將在八月出版問世。本文將其中Spring Cloud Stream應用與自定義Rocketmq Binder的內容抽取出來,主要介紹實現Spring Cloud Stream 的RocketMQ繫結器。

Stream的Binder機制

在上一篇中,介紹了Spring Cloud Stream基本的概念及其程式設計模型。除此之外,Spring Cloud Stream提供了Binder介面來用於和外部訊息佇列進行繫結。本文將講述Binder SPI的基本概念,主要元件和實現細節。 Binder SPI通過一系列的介面,工具類和檢測機制提供了與外部訊息佇列繫結的繫結器機制。SPI的關鍵點是Binder介面,這個介面負責提供和外部訊息佇列進行繫結的具體實現。

public interface Binder<T, C extends ConsumerProperties, P extends ProducerProperties> {
    Binding<T> bindConsumer(String name, String group, T inboundBindTarget, C consumerProperties);
    Binding<T> bindProducer(String name, T outboundBindTarget, P producerProperties);
}
複製程式碼

一個典型的自定義Binder元件實現應該包括以下幾點:

  • 一個實現Binder介面的類。
  • 一個Spring的@Configuration類來建立上述型別的例項。
  • 在classpath上一個包含自定義Binder相關配置類的META-INF/spring.binders檔案,比如說:
kafka:\
org.springframework.cloud.stream.binder.kafka.config.KafkaBinderConfiguration
複製程式碼

Spring Cloud Stream基於Binder SPI的實現來進行channel和訊息佇列的繫結任務。不同型別的訊息佇列中介軟體實現了不同的繫結器Binder。比如說:Spring-Cloud-Stream-Binder-Kafka是針對Kafka的Binder實現,而Spring-Cloud-Stream-Binder-Rabbit則是針對RabbitMQ的Binder實現。

Spring Cloud Stream依賴於Spring Boot的自動配置機制來配置Binder。如果一個Binder實現在專案的classpath中被發現,Spring Cloud Stream將會自動使用它。比如說,一個Spring Cloud Stream專案需要繫結RabbitMQ中介軟體的Binder,在pom檔案中加入下面的依賴來輕鬆實現。

<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-stream-binder-rabbit</artifactId>
</dependency>
複製程式碼

Binder For RocketMQ

Spring Cloud Stream為接入不同的訊息佇列提供了一整套的自定義機制,通過為每個訊息隊裡開發一個Binder來接入該訊息佇列。目前官方認定的Binder為rabbitmq binder和kafka binder。但是開發人員可以基於Stream Binder的機制來制定自己的Binder。下面我們就構建一個簡單的RocketMQ的Binder。

配置類

需要在resources/META-INF/spring.binders檔案中配置有關RocketMQ的Configuration類,該配置類會使用@Import來匯入為RocketMQ制定的RocketMessageChannelBinderConfiguration

rocket:\
org.springframework.cloud.stream.binder.rocket.config.RocketServiceAutoConfiguration
複製程式碼

RocketMessageChannelBinderConfiguration將會提供兩個極其重要的bean例項,分別為RocketMessageChannelBinderRocketExchangeQueueProvisionerRocketMessageChannelBinder主要是用於channel和訊息佇列的繫結,而RocketExchangeQueueProvisioner則封裝了RocketMQ的相關API,可以用於建立訊息佇列的基礎元件,比如說佇列,交換器等。

@Configuration
public class RocketMessageChannelBinderConfiguration {
    @Autowired
    private ConnectionFactory rocketConnectionFactory;
    @Autowired
    private RocketProperties  rocketProperties;
    @Bean
    RocketMessageChannelBinder rocketMessageChannelBinder() throws Exception {
        RocketMessageChannelBinder binder = new RocketMessageChannelBinder(this.rocketConnectionFactory,
                this.rocketProperties, provisioningProvider());
        return binder;
    }
    @Bean
    RocketExchangeQueueProvisioner provisioningProvider() {
        return new RocketExchangeQueueProvisioner(this.rocketConnectionFactory);
    }
}
複製程式碼

RocketMessageChannelBinder繼承了抽象類AbstractMessageChannelBinder,並實現了#producerMessageHandler和#createConsumerEndpoint函式。

MessageHandler有向訊息佇列傳送訊息的能力,#createProducerMessageHandler函式就是為了建立MessageHandler物件,來將輸出型Channel的訊息傳送到訊息佇列上。

protected MessageHandler createProducerMessageHandler(
        ProducerDestination destination,             
        ExtendedProducerProperties<RocketProducerProperties> producerProperties,                    
        MessageChannel errorChannel) 
        throws Exception {
    final AmqpOutboundEndpoint endpoint = new AmqpOutboundEndpoint(
            buildRocketTemplate(producerProperties.getExtension(), errorChannel != null));
    return endpoint;
}
複製程式碼

MessageProducer能夠從訊息佇列接收訊息,並將該訊息傳送輸入型Channel。

@Override
protected MessageProducer createConsumerEndpoint(ConsumerDestination consumerDestination, String group,
                                                    ExtendedConsumerProperties<RocketConsumerProperties> properties) throws Exception {
    SimpleRocketMessageListenerContainer listenerContainer = new SimpleRocketMessageListenerContainer();
    RocketInboundChannelAdapter rocketInboundChannelAdapter = new RocketInboundChannelAdapter(listenerContainer);
    return rocketInboundChannelAdapter;
}
複製程式碼

訊息接收功能的實現流程

類似於RabbitMQ的Binder,需要實現下面一系列的類來實現從RocketMQ到對應MessageChannel的訊息傳遞。

  1. RocketBlockingQueueConsumer.InnerConsumer實現了MessageListenerConcurrently來接收RocketMQ傳遞的訊息。
  2. RocketBlockingQueueConsumer將InnerConsumer註冊給RocketMQ的DefaultMQPushConsumer來接收RocketMQ傳遞過來的訊息,並儲存在自身的阻塞佇列中。供SimpleRocketMessageListenerContainer獲取。
  3. SimpleRocketMessageListenerContainer,啟動一個執行緒來不停從RocketBlockingQueueConsumer獲取訊息,然後呼叫RocketInboundChannelAdapter.Listener的回撥函式,將訊息傳遞給RocketInboundChannelAdapter。
  4. RocketInboundChannelAdapter.Listener供SimpleRocketMessageListenerContainer回撥,將訊息傳送給RocketInboundChannelAdapter。
  5. RocketInboundChannelAdapter,接受SimpleRocketMessageListenerContainer傳遞過來的訊息,然後通過MessageTemplate傳送給相應的MessageChannel。從而傳遞給由@StreamListener的修飾的函式。

InnerConsumer接收RocketMQ訊息

InnerConsumer實現的MessageListenerConcurrently介面是RocketMQ中用於併發接受非同步訊息的介面,該介面可以接收到RocketMQ傳送過來的非同步訊息。而InnerConsumer在接受到訊息之後,會將訊息封裝成RocketDelivery加入到阻塞佇列中。

RocketBlockingQueueConsumer有一個阻塞佇列來儲存RocketMQ傳遞給RocketBlockingQueueConsumer.InnerConsumer的訊息,而nextMessage函式可以從阻塞佇列中拉取一個訊息並返回。

AsyncMessageProcessingConsumer獲取訊息

SimpleRocketMessageListenerContainer.AsyncMessageProcessingConsumer是實現了Runnable介面,在run()介面中會無限迴圈地呼叫SimpleRocketMessageListenerContainer本身的receiveAndExecute。

@Override
public void run() {
    if (!isActive()) {
        return;
    }
    try {
        //只要consumer的狀態正常,就會一直迴圈
        while (isActive(this.consumer) || this.consumer.hasDelivery() || !this.consumer.cancelled()) {
            try {
                boolean receivedOk = receiveAndExecute(this.consumer);
            }
            catch (ListenerExecutionFailedException ex) {
                if (ex.getCause() instanceof NoSuchMethodException) {
                    throw new FatalListenerExecutionException("Invalid listener", ex);
                }
            }
            catch (AmqpRejectAndDontRequeueException rejectEx) {
            } catch (Throwable e) {
            }
        }
    } catch (Exception e) {
    }
    finally {
        if (getTransactionManager() != null) {
            ConsumerChannelRegistry.unRegisterConsumerChannel();
        }
    }
    this.start.countDown();
    if (!isActive(this.consumer) || aborted) {
        this.consumer.stop();
    }
    else {
        restart(this.consumer);
    }
}
複製程式碼

函式#receiveAndExecute最終的作用就是呼叫RocketBlockingQueueConsumer的nextMessage,然後再將訊息呼叫messageListener.onMessage函式將訊息傳遞出去。

初始化RocketBlockingQueueConsumer和AsyncMessageProcessingConsumer

SimpleRocketMessageListenerContainer的doStart函式會初始化RocketBlockingQueueConsumer並且啟動SimpleRocketMessageListenerContainer的AsyncMessageProcessingConsumer會無限迴圈地從RocketBlockingQueueConsumer中獲取RocketMQ傳遞過來的訊息。

private void doStart() {
    synchronized (this.lifecycleMonitor) {
        this.active = true;
        this.running = true;
        this.lifecycleMonitor.notifyAll();
    }
    synchronized (this.consumersMonitor) {
        if (this.consumers != null) {
            throw new IllegalStateException("A stopped container should not have consumers");
        }
        //初始化Consumer
        int newConsumers = initializeConsumers();
        if (this.consumers == null) {
            return;
        }
        if (newConsumers <= 0) {
            return;
        }
        Set<SimpleRocketMessageListenerContainer.AsyncMessageProcessingConsumer> processors =
                new HashSet<>();
        //對於每個RocketBlockingQueueConsumer啟動一個
        //AsyncMessageProcessingConsumer來執行任務
        for (RocketBlockingQueueConsumer consumer : this.consumers) {
            SimpleRocketMessageListenerContainer.AsyncMessageProcessingConsumer
                    processor = new SimpleRocketMessageListenerContainer.AsyncMessageProcessingConsumer(consumer);
            processors.add(processor);
            getTaskExecutor().execute(processor);
        }
    }
}
複製程式碼

傳送訊息給MessageChannel

RocketInboundChannelAdapter實現了MessageProducer介面。它主要將SimpleRocketMessageListenerContainer傳遞過來的訊息經過MessageTemplate傳遞給MessageChannel。

接下來則是RocketInboundChannelAdapter.Listener的實現,它就是RocketBlockingQueueConsumer.nextMessage函式中的messageListener。

public class Listener implements ChannelAwareMessageListener, RetryListener {
    public void onMessage(Message message, Channel channel) throws Exception {
        try {
            this.createAndSend(message, channel);
        } catch (RuntimeException var7) {
            if (RocketInboundChannelAdapter.this.getErrorChannel() == null) {
                throw var7;
            }
       RocketInboundChannelAdapter.this.getMessagingTemplate().send(RocketInboundChannelAdapter.this.getErrorChannel(), RocketInboundChannelAdapter.this.buildErrorMessage((org.springframework.messaging.Message)null, new ListenerExecutionFailedException("Message conversion failed", var7, message)));
        }
    }
    private void createAndSend(Message message, Channel channel) {
        org.springframework.messaging.Message<Object> messagingMessage = this.createMessage(message, channel);
        RocketInboundChannelAdapter.this.sendMessage(messagingMessage);
    }
    private org.springframework.messaging.Message<Object> createMessage(Message message, Channel channel) {
        Object payload = RocketInboundChannelAdapter.this.messageConverter.fromMessage(message);
        org.springframework.messaging.Message<Object> messagingMessage = RocketInboundChannelAdapter.this.getMessageBuilderFactory().withPayload(payload).build();
        return messagingMessage;
    }
}
複製程式碼

RocketMQ的管理器

RocketProvisioningProvider實現了ProvisioningProvider介面,它有兩個函式:provisionProducerDestination和provisionConsumerDestination,分別用於建立ProducerDestination和ConsumerDestination。RocketProvisioningProvider的實現類似於RabbitProvisioningProvider。只不過在宣告佇列,交換器和繫結時使用了RocketAdmin所實現的RocketMQ的相關API。

總結

本文概要介紹了Spring Cloud Stream的Rocketmq繫結器的實現,限於篇幅不展開具體的程式碼講解。讀者感興趣,可以關注GitHub上的程式碼。根據Spring Cloud Stream抽象的介面,我們可以自由地實現各種訊息佇列的繫結器。

專案GitHub地址:https://github.com/ztelur/spring-cloud-stream-binder-rocket 推薦閱讀:Spring Cloud Stream應用與自定義RocketMQ Binder:程式設計模型

訂閱最新文章,歡迎關注我的公眾號

微信公眾號

相關文章