應用場景
之前我們已經通過《Spring Cloud Stream消費失敗後的處理策略(一):自動重試》一文介紹了Spring Cloud Stream預設的訊息重試功能。本文將介紹RabbitMQ的binder提供的另外一種重試功能:重新入隊。
動手試試
準備一個會消費失敗的例子,可以直接沿用前文的工程,也可以新建一個,然後建立如下程式碼的邏輯:
@EnableBinding(TestApplication.TestTopic.class)
@SpringBootApplication
public class TestApplication {
public static void main(String[] args) {
SpringApplication.run(TestApplication.class, args);
}
@RestController
static class TestController {
@Autowired
private TestTopic testTopic;
/**
* 訊息生產介面
*
* @param message
* @return
*/
@GetMapping("/sendMessage")
public String messageWithMQ(@RequestParam String message) {
testTopic.output().send(MessageBuilder.withPayload(message).build());
return "ok";
}
}
/**
* 訊息消費邏輯
*/
@Slf4j
@Component
static class TestListener {
private int count = 1;
@StreamListener(TestTopic.INPUT)
public void receive(String payload) {
log.info("Received payload : " + payload + ", " + count);
throw new RuntimeException("Message consumer failed!");
}
}
interface TestTopic {
String OUTPUT = "example-topic-output";
String INPUT = "example-topic-input";
@Output(OUTPUT)
MessageChannel output();
@Input(INPUT)
SubscribableChannel input();
}
}
內容很簡單,既包含了訊息的生產,也包含了訊息消費。訊息消費的時候主動丟擲了一個異常來模擬訊息的消費失敗。
在啟動應用之前,還要記得配置一下輸入輸出通道對應的物理目標(exchange或topic名)、並設定一下分組,比如:
spring.cloud.stream.bindings.example-topic-input.destination=test-topic
spring.cloud.stream.bindings.example-topic-input.group=stream-exception-handler
spring.cloud.stream.bindings.example-topic-input.consumer.max-attempts=1
spring.cloud.stream.rabbit.bindings.example-topic-input.consumer.requeue-rejected=true
spring.cloud.stream.bindings.example-topic-output.destination=test-topic
完成了上面配置之後,啟動應用並訪問localhost:8080/sendMessage?message=hello
介面來傳送一個訊息到MQ中了,此時可以看到程式不斷的丟擲了訊息消費異常。這是由於這裡我們多加了一個配置:spring.cloud.stream.rabbit.bindings.example-topic-input.consumer.requeue-rejected=true
。在該配置作用之下,訊息消費失敗之後,並不會將該訊息拋棄,而是將訊息重新放入佇列,所以訊息的消費邏輯會被重複執行,直到這條訊息消費成功為止。
深入思考
在完成了上面的這個例子之後,可能讀者會有下面兩個常見問題:
問題一:之前介紹的Spring Cloud Stream預設提供的預設功能(spring.cloud.stream.bindings.example-topic-input.consumer.max-attempts)與本文所說的重入佇列實現的重試有什麼區別?
Spring Cloud Stream預設提供的預設功能只是對處理邏輯的重試,它們的處理邏輯是由同一條訊息觸發的。而本文所介紹的重新入隊史通過重新將訊息放入佇列而觸發的,所以實際上是收到了多次訊息而實現的重試。
問題二:如上面的例子那樣,消費一直不成功,這些不成功的訊息會被不斷堆積起來,如何解決這個問題?
對於這個問題,我們可以聯合前文介紹的DLQ佇列來完善訊息的異常處理。
我們只需要增加如下配置,自動繫結dlq佇列:
spring.cloud.stream.rabbit.bindings.example-topic-input.consumer.auto-bind-dlq=true
然後改造一下訊息處理程式,可以根據業務情況,為進入dlq佇列增加一個條件,比如下面的例子:
@StreamListener(TestTopic.INPUT)
public void receive(String payload) {
log.info("Received payload : " + payload + ", " + count);
if (count == 3) {
count = 1;
throw new AmqpRejectAndDontRequeueException("tried 3 times failed, send to dlq!");
} else {
count ++;
throw new RuntimeException("Message consumer failed!");
}
}
設定了計數器count,當count為3的時候丟擲AmqpRejectAndDontRequeueException
這個特定的異常。此時,當只有當丟擲這個異常的時候,才會將訊息放入DLQ佇列,從而不會造成嚴重的堆積問題。
程式碼示例
本文示例讀者可以通過檢視下面倉庫的中的stream-exception-handler-4
專案:
如果您對這些感興趣,歡迎star、follow、收藏、轉發給予支援!
以下專題教程也許您會有興趣
本文首發:http://blog.didispace.com/spring-cloud-starter-finchley-7-5/