前言: 本文作者張天,節選自筆者與其合著的《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例項,分別為RocketMessageChannelBinder
和RocketExchangeQueueProvisioner
。RocketMessageChannelBinder
主要是用於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的訊息傳遞。
- RocketBlockingQueueConsumer.InnerConsumer實現了MessageListenerConcurrently來接收RocketMQ傳遞的訊息。
- RocketBlockingQueueConsumer將InnerConsumer註冊給RocketMQ的DefaultMQPushConsumer來接收RocketMQ傳遞過來的訊息,並儲存在自身的阻塞佇列中。供SimpleRocketMessageListenerContainer獲取。
- SimpleRocketMessageListenerContainer,啟動一個執行緒來不停從RocketBlockingQueueConsumer獲取訊息,然後呼叫RocketInboundChannelAdapter.Listener的回撥函式,將訊息傳遞給RocketInboundChannelAdapter。
- RocketInboundChannelAdapter.Listener供SimpleRocketMessageListenerContainer回撥,將訊息傳送給RocketInboundChannelAdapter。
- 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:程式設計模型