使用Spring Cloud Stream和RabbitMQ實現事件驅動的微服務

banq發表於2019-01-10

讓我們展示如何使用Spring Cloud Stream來設計事件驅動的微服務。首先,Spring Cloud Stream首先有什麼好處?因為Spring AMPQ提供了訪問AMPQ工件所需的一切。如果您不熟悉Spring AMPQ,請檢視此repo,其中包含許多有用的示例。那麼為什麼要使用Spring Cloud Stream ......?

Spring Cloud Stream概念

  • Spring Cloud Stream透過Binder概念將使用過的訊息代理與Spring Integration訊息通道繫結在一起。支援RabbitMQ和Kafka。
  • Spring Cloud Stream將基礎架構配置從程式碼中分離為屬性檔案。這意味著即使您更改了底層代理,您的Spring Integration程式碼也將是相同的!

示例中的Spring Cloud Stream概念(RabbitMQ)
讓我們有一個名為streamInput的交換,它有兩個佇列streamInput.cities和streamInput.persons。現在讓我們將這兩個佇列插入兩個訊息通道citiesChannel和personsChannel來消費來自它的傳入訊息。使用Spring AMPQ,您需要建立SimpleMessageListenerContainer並在程式碼中連線基礎結構。但這有很多樣板程式碼。使用Spring Cloud Stream,您可以將AMPQ配置分離到屬性檔案:

spring.cloud.stream.bindings.citiesChannel.destination=streamInput
spring.cloud.stream.bindings.citiesChannel.group=cities
spring.cloud.stream.rabbit.bindings.citiesChannel.consumer.durableSubscription=true
spring.cloud.stream.rabbit.bindings.citiesChannel.consumer.bindingRoutingKey=cities

spring.cloud.stream.bindings.personsChannel.destination=streamInput
spring.cloud.stream.bindings.personsChannel.group=persons
spring.cloud.stream.rabbit.bindings.personsChannel.consumer.durableSubscription=true
spring.cloud.stream.rabbit.bindings.personsChannel.consumer.bindingRoutingKey=persons


配置詳細資訊
在類路徑上使用RabbitMQ Binder,每個目標都對映到TopicExchange。在示例中,我建立了名為streamInput的TopicExchange, 並將其附加到兩個訊息通道citiesChannel和personsChannel。

spring.cloud.stream.bindings.citiesChannel.destination = streamInput 
spring.cloud.stream.bindings.personsChannel.destination = streamInput

現在您需要了解RabbitMQ繫結器的靈感來自Kafka,佇列的消費者被分組到消費者組中,其中只有一個消費者將獲得訊息。這是有道理的,因為您可以輕鬆擴充套件消費者。
因此,讓我們建立兩個佇列streamInput.persons和streamInput.cities並將它們附加到streamInput TopicExchange和提到的訊息通道

# This will create queue "streamInput.cities" connected to message channel citiesChannel where input messages will land.
spring.cloud.stream.bindings.citiesChannel.group=cities 

# Durable subscription, of course.
spring.cloud.stream.rabbit.bindings.citiesChannel.consumer.durableSubscription=true 

# AMPQ binding to exchange (previous spring.cloud.stream.bindings.<channel name>.destination settings).
# Only messages with routingKey = 'cities' will land here.
spring.cloud.stream.rabbit.bindings.citiesChannel.consumer.bindingRoutingKey=cities 

spring.cloud.stream.bindings.personsChannel.group=persons
spring.cloud.stream.rabbit.bindings.personsChannel.consumer.durableSubscription=true
spring.cloud.stream.rabbit.bindings.personsChannel.consumer.bindingRoutingKey=persons


連線屬性到Spring Integration
好的,到目前為止我建立了兩個佇列。StreamInput.cities繫結到citiesChannel。StreamInput.persons繫結到peopleChannel。
<destination>.<group>是Spring Cloud Stream約定的佇列命名,現在讓我們將它連線到Spring Integration:

package com.example.spring.cloud.configuration;

import org.springframework.cloud.stream.annotation.Input;
import org.springframework.messaging.SubscribableChannel;

/**
 * Created by tomask79 on 30.03.17.
 */
public interface SinkRabbitAPI {

    String INPUT_CITIES = "citiesChannel";

    String INPUT_PERSONS = "personsChannel";

    @Input(SinkRabbitAPI.INPUT_CITIES)
    SubscribableChannel citiesChannel();

    @Input(SinkRabbitAPI.INPUT_PERSONS)
    SubscribableChannel personsChannel();
}

Spring Boot啟動時載入這個屬性

package com.example.spring.cloud;

import com.example.spring.cloud.configuration.SinkRabbitAPI;
import com.example.spring.cloud.configuration.SourceRabbitAPI;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.stream.annotation.EnableBinding;
import org.springframework.scheduling.annotation.EnableScheduling;

@SpringBootApplication
@EnableBinding({SinkRabbitAPI.class})
public class StreamingApplication {

    public static void main(String[] args) {
        SpringApplication.run(StreamingApplication.class, args);
    }
}


在此之後,我們可以建立消費者從繫結的訊息通道中的佇列接收訊息:

import com.example.spring.cloud.configuration.SinkRabbitAPI;
import com.example.spring.cloud.configuration.SourceRabbitAPI;
import org.springframework.cloud.stream.annotation.StreamListener;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.handler.annotation.SendTo;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;

/**
 * Created by tomask79 on 30.03.17.
 */
@Service
public class ProcessingAMPQEndpoint {

    @StreamListener(SinkRabbitAPI.INPUT_CITIES)
    public void processCity(final String city) {
        System.out.println("Trying to process input city: "+city);
    }

    @StreamListener(SinkRabbitAPI.INPUT_PERSONS)
    public void processPersons(final String person) {
        System.out.println("Trying to process input person: "+person);
    }
}

RabbitMQ繫結器和代理配置
Spring Cloud Stream如何知道在哪裡尋找訊息中介軟體?如果在類路徑中找到RabbitMQ繫結器,則使用預設RabbitMQ主機(localhost)和埠(5672)連線到RabbitMQ伺服器。如果您的訊息中介軟體配置在不同埠,則需要配置屬性:

spring:
  cloud:
    stream:
      bindings:
        ...
      binders:
          rabbitbinder:
            type: rabbit
            environment:
              spring:
                rabbitmq:
                  host: rabbitmq
                  port: 5672
                  username: XXX
                  password: XXX


測試訊息消費

Started StreamingApplication in 6.513 seconds (JVM running for 6.92) 
Trying to process input city: sdjfjljksdflkjsdflkjsdfsfd
Trying to process input person: sdjfjljksdflkjsdflkjsdfsfd


使用Spring Cloud Stream重新傳遞訊息
您通常希望在進入DLX交換之前再次嘗試接收訊息。首先,讓我們配置Spring Cloud Stream嘗試重新傳送失敗訊息的次數:

spring.cloud.stream.bindings.personsChannel.consumer.maxAttempts = 6

這意味著如果從streamInput.persons佇列接收的訊息出錯,那麼Spring Cloud Stream將嘗試重新傳送六次。讓我們試試,首先讓我們修改接收端點以模擬接收崩潰:

 @StreamListener(SinkRabbitAPI.INPUT_PERSONS)
    public void processPersons(final String person) {
        System.out.println("Trying to process input person: "+person);
        throw new RuntimeException();
    }


如果我現在嘗試使用人員路由鍵將某些內容釋出到streamInput交換中,那麼這應該是輸出:

Trying to process input person: sfsdfsdfsd
Trying to process input person: sfsdfsdfsd
Trying to process input person: sfsdfsdfsd
Trying to process input person: sfsdfsdfsd
Trying to process input person: sfsdfsdfsd
Trying to process input person: sfsdfsdfsd
 Retry Policy Exhausted
        at org.springframework.amqp.rabbit.retry.RejectAndDontRequeueRecoverer.recover
(RejectAndDontRequeueRecoverer.java:45) ~[spring-rabbit-1.7.0.RELEASE.jar! /:na]
        at org.springframework.amqp.rabbit.config.StatelessRetryOperationsInterc       


建議將Spring Cloud Stream 用於事件驅動的MicroServices,因為它可以節省時間,而且您不需要為Java中的AMPQ基礎架構編寫樣板程式碼。

相關文章