Spring Cloud 學習筆記 - No.7 消息驅動 Stream

請先閱讀之前的內容:

Spring Cloud Stream

Spring Cloud Stream 是一個用來為微服務應用構建消息驅動能力的框架,為一些供應商的消息中間件產品提供了個性化的自動化配置實現,并且引入了發布-訂閱、消費組以及消息分區這三個核心概念。
簡單的說,Spring Cloud Stream 本質上就是整合了 Spring Boot 和 Spring Integration,實現了一套輕量級的消息驅動的微服務框架。
通過使用 Spring Cloud Stream,可以有效地簡化開發人員對消息中間件的使用復雜度,讓系統開發人員可以有更多的精力關注于核心業務邏輯的處理。目前為止 Spring Cloud Stream 只支持下面兩個消息中間件的自動化配置:

構建一個 Spring Cloud Stream 消費者

我們利用之前創建的 eureka-consumer 項目。
首先在 pom.xml 中添加如下的依賴:

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-stream</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-stream-rabbit</artifactId>
</dependency>

其中 spring-cloud-starter-stream-rabbit 是 Spring Cloud Stream 對 RabbitMQ 支持的封裝,其中包含了對 RabbitMQ 的自動化配置等內容。

隨后創建用于接收來自 RabbitMQ 消息的消費者 SinkReceiver

@EnableBinding(Sink.class)
public class SinkReceiver {

    private static Logger logger = LoggerFactory.getLogger(SinkReceiver.class);

    @StreamListener(Sink.INPUT)
    public void receive(Object payload) {
        logger.info("Received: " + payload);
    }

}
  • @EnableBinding 注解用來指定一個或多個定義了 @Input@Output 注解的接口,以此實現對消息通道(Channel)的綁定。
    • 綁定了 Sink 接口,該接口是 Spring Cloud Stream 中默認實現的對輸入消息通道綁定的定義
    • Spring Cloud Stream 還默認實現了綁定輸出消息通道的 Source 接口
    • 還有結合了 SinkSourceProcessor 接口
  • @StreamListener 注解用來將被修飾的方法注冊為消息中間件上數據流的事件監聽器,注解中的屬性值對應了監聽的消息通道名。

重啟項目,從日志中可以看到聲明了一個名為 input.anonymous.cWlqMyH9Tm--INXERE6nhQ 的隊列,并通過 RabbitMessageChannelBinder 將自己綁定為它的消費者。

c.s.b.r.p.RabbitExchangeQueueProvisioner : declaring queue for inbound: input.anonymous.cWlqMyH9Tm--INXERE6nhQ, bound to: input

這些信息我們也能在 RabbitMQ 的控制臺中發現它們:


RabbitMQ 的控制臺

點擊進去,通過 Publish Message 功能來發送一條消息到該隊列中:


通過 Publish Message 功能來發送一條消息到該隊列中

從下面的日志可以看出 SinkReceiver 讀取了消息隊列中的內容,由于我們沒有對消息進行序列化,所以輸出的只是該對象的引用:

[m--INXERE6nhQ-1] com.example.SinkReceiver                 : Received: [B@beb7ce8

在上面的操作中,我們并沒有手動去配置 RabbitMQ 的信息,比如 IP,端口等等,這是基于 Spring Boot 的設計理念,提供了對 RabbitMQ 默認的自動化配置。當然,我們可以手動在 application.properties 文件中去配置,例如:

spring.cloud.stream.bindings.input.destination=my_destination

spring.rabbitmq.host=127.0.0.1
spring.rabbitmq.port=5672
spring.rabbitmq.username=root
spring.rabbitmq.password=root

編寫消費消息的單元測試用例

@RunWith(SpringRunner.class)
@EnableBinding(value = {SinkReceiverTests.SinkSender.class})
public class SinkReceiverTests {

    @Autowired
    private SinkSender sinkSender;

    @Test
    public void sinkSenderTester() {
        sinkSender.output().send(MessageBuilder.withPayload("Testing Message").build());
    }

    public interface SinkSender {

        String OUTPUT = "input";

        @Output(SinkSender.OUTPUT)
        MessageChannel output();

    }

}

在上面的單元測試中,我們通過 @Output(SinkSender.OUTPUT) 定義了一個輸出通過,而該輸出通道的名稱為 input,與前文中的 Sink 中定義的消費通道同名,所以這里的單元測試與前文的消費者程序組成了一對生產者與消費者。
運行該單元測試,日志可以看出 SinkReceiver 讀取了消息隊列中的內容:

[m--INXERE6nhQ-1] com.example.SinkReceiver                 : Received: [B@89040a9

Spring Cloud Stream 應用模型

圖片引自:https://docs.spring.io/spring-cloud-stream/docs/Fishtown.BUILD-SNAPSHOT/reference/htmlsingle/

Spring Cloud Stream 應用模型

綁定器

Spring Cloud Stream 構建的應用程序與消息中間件之間是通過綁定器: Binder 相關聯的,綁定器對于應用程序而言起到了隔離作用,它使得不同消息中間件的實現細節對應用程序來說是透明的。
當我們需要升級消息中間件,或是更換其他消息中間件產品時,我們要做的就是更換它們對應的 Binder 綁定器而不需要修改任何Spring Boot的應用邏輯。

所以對于每一個 Spring Cloud Stream 的應用程序來說,它不需要知曉消息中間件的通信細節,它只需要知道 Binder 對應用程序提供的概念去實現即可,而這個概念就是消息通道:Channel。

發布-訂閱模式

消息會通過共享的 Topic 主題進行廣播,消息消費者在訂閱的主題中收到它并觸發自身的業務邏輯處理。

這里所提到的 Topic 主題是 Spring Cloud Stream 中的一個抽象概念,用來代表發布共享消息給消費者的地方。
在不同的消息中間件中,Topic 可能對應著不同的概念,比如:在 RabbitMQ 中的它對應了 Exchange、而在 Kakfa 中則對應了 Kafka 中的 Topic。

在上面的例子中,應用啟動的時候,在 RabbitMQ 的 Exchange 中也創建了一個名為 input 的 Exchange交換器。例如我們分別以 3001 和 3002 兩個端口啟動 eureka-consumer 項目。
可以看到 Queues 中有兩個 Queue:

Queues 中有兩個 Queue

可以看到 Channels 中有兩個 Channel:


Channels 中有兩個 Channel

可以看出 Exchanges 中只有一個名稱為 input 的 Exchange,即 Topic 主題。但是點進去,可以看出這個名稱為 input 的 Exchange 有綁定了兩個消息隊列:

Exchanges 中只有一個名稱為 input 的 Exchange

這個名稱為 input 的 Exchange 有兩個 Bindings

如果我們通過 Exchange 頁面的 Publish Message 來發布消息,可以發現兩個啟動的應用程序都輸出了消息內容。

圖片引自 http://blog.didispace.com/spring-cloud-starter-dalston-7-2/

發布-訂閱模式

相對于點對點隊列實現的消息通信來說,Spring Cloud Stream 采用的發布-訂閱模式可以有效的降低消息生產者與消費者之間的耦合,當我們需要對同一類消息增加一種處理方式時,只需要增加一個應用程序并將輸入通道綁定到既有的 Topic 中就可以實現功能的擴展,而不需要改變原來已經實現的任何內容。

消費組

很多情況下,消息生產者發送消息給某個具體微服務時,只希望被消費一次,但是上面我們啟動兩個應用(3001 和 3002 兩個端口),這個消息出現了被重復消費兩次的情況。
為了解決這個問題,在 Spring Cloud Stream 中提供了消費組的概念。

如果在同一個主題上的應用需要啟動多個實例的時候,我們可以通過spring.cloud.stream.bindings.<channelName>.group 屬性為應用指定一個組名,這樣這個應用的多個實例在接收到消息的時候,只會有一個成員真正的收到消息并進行處理。

例如,我們在 eureka-consumer 項目的配置中增加:

spring.cloud.stream.bindings.input.group=eureka-consumer-input-group

重啟兩個端口的實例,隨后通過 Exchange 頁面的 Publish Message 來發布消息,可以發現只有一個啟動的應用程序都輸出了消息內容。并且有時候是 3001 端口的實例處理,有時候是 3002 端口的實例處理。
也就是說,對于同一條消息,它多次到達之后可能是由不同的實例進行消費的。

消息分區

在上面的實驗中可以看到,消費組并無法控制消息具體被哪個實例消費。但是對于一些業務場景,就需要對于一些具有相同特征的消息每次都可以被同一個消費實例處理。比如:一些用于監控服務,為了統計某段時間內消息生產者發送的報告內容,監控服務需要在自身內容聚合這些數據,那么消息生產者可以為消息增加一個固有的特征 ID 來進行分區,使得擁有這些 ID 的消息每次都能被發送到一個特定的實例上實現累計統計的效果,否則這些數據就會分散到各個不同的節點導致監控結果不一致的情況。

而消息分區概念的引入就是為了解決這樣的問題:當生產者將消息數據發送給多個消費者實例時,保證擁有共同特征的消息數據始終是由同一個消費者實例接收和處理。

例如,我們在 eureka-consumer 項目的配置中增加:

spring.cloud.stream.bindings.input.consumer.partitioned=true
spring.cloud.stream.instanceCount=2
spring.cloud.stream.instanceIndex=0
  • spring.cloud.stream.bindings.input.consumer.partitioned:通過該參數開啟消費者分區功能;
  • spring.cloud.stream.instanceCount:該參數指定了當前消費者的總實例數量;
  • spring.cloud.stream.instanceIndex:該參數設置當前實例的索引號,從0開始,最大值為spring.cloud.stream.instanceCount - 1。

Spring Cloud Stream VS Spring Cloud Bus

我們在 Spring Cloud 學習筆記 - No.3 分布式配置 Config 中使用了 Spring Cloud Bus(結合了 RabbitMQ),那么 Stream 和 Bus 的區別是什么?

  • Spring Cloud Stream 構建消息驅動微服務
    • building highly scalable event-driven microservices connected with shared messaging systems.
  • Spring Cloud Bus 廣播(例如配置統一管理)和監控
    • Spring Cloud Bus links nodes of a distributed system with a lightweight message broker. This can then be used to broadcast state changes (e.g. configuration changes) or other management instructions.

RabbitMQ 負載均衡

在上面的例子中,我們始終只有一個 RabbitMQ 實例。在生產環境中,我們可能需要多個 RabbitMQ 實例來實現高并發和高可用。
參見:


引用:
程序猿DD Spring Cloud基礎教程
Spring Cloud構建微服務架構:消息驅動的微服務(入門)【Dalston版】
Spring Cloud構建微服務架構:消息驅動的微服務(核心概念)【Dalston版】
Spring Cloud Dalston中文文檔

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • iOS上所有和音頻相關的都在CoreAudio里面。但是CoreAuido是個非常龐大的庫,剛開始入門多半會被其復...
    偶是星爺閱讀 1,573評論 0 1
  • 沒錯,是“同姓戀”,不是“同性戀”哦! 現在社會的包容度高,什么同性戀,姐弟戀,試婚等都能微笑面對。可我老家那小村...
    月兒上山了閱讀 1,727評論 24 15
  • 每天喊著要改變命運,改變生活,賺很多錢,過很貴的生活,然后拖延癥把這些美好的理想一拖再拖。 為什么拖延,其中一點就...
    月兒林飛飛閱讀 389評論 0 0
  • 窗外追趕的雨滴嬉戲 凌亂了廊前盛開的月季 那把黑色的油紙傘,掠過窗前 我看到頑皮的雨花在親吻明凈的玻璃窗葉 旖旎的...
    誰言1990閱讀 640評論 3 26