Spring Cloud Stream如何處理訊息重複消費?

程式猿DD發表於2018-11-26

最近收到好幾個類似的問題:使用Spring Cloud Stream操作RabbitMQ或Kafka的時候,出現訊息重複消費的問題。通過溝通與排查下來主要還是使用者對消費組的認識不夠。其實,在之前的博文以及《Spring Cloud微服務實戰》一書中都有提到關於消費組的概念以及作用。

那麼什麼是消費組呢?為什麼要用消費組?它解決什麼問題呢?摘錄一段之前博文的內容,來解答這些疑問:

通常在生產環境,我們的每個服務都不會以單節點的方式執行在生產環境,當同一個服務啟動多個例項的時候,這些例項都會繫結到同一個訊息通道的目標主題(Topic)上。預設情況下,當生產者發出一條訊息到繫結通道上,這條訊息會產生多個副本被每個消費者例項接收和處理(出現上述重複消費問題)。但是有些業務場景之下,我們希望生產者產生的訊息只被其中一個例項消費,這個時候我們需要為這些消費者設定消費組來實現這樣的功能。

詳細也可檢視原文:訊息驅動的微服務(消費組)

下面,通過一個例子來看看如何使用消費組:

問題重現

構建訊息消費端

第一步:建立繫結介面,繫結example-topic輸入通道(預設情況下,會繫結到RabbitMQ的同名Exchange或Kafaka的同名Topic)。

interface ExampleBinder {

    String NAME = "example-topic";

    @Input(NAME)
    SubscribableChannel input();

}

第二步:對上述輸入通道建立監聽與處理邏輯。

@EnableBinding(ExampleBinder.class)
public class ExampleReceiver {

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

    @StreamListener(ExampleBinder.NAME)
    public void receive(String payload) {
        logger.info("Received: " + payload);
    }

}

第三步;建立應用主類和配置檔案

@SpringBootApplication
public class ExampleApplication {

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

}
spring.application.name=stream-consumer-group
server.port=0

這裡設定server.port=0,以方便在本地啟動多例項來重現問題。

完成上述操作之後,啟動兩個該應用的例項,以備後續呼叫。

構建訊息生產端

比較簡單,需要注意的是,使用@Output建立一個同名的輸出繫結,這樣發出的訊息才能被上述啟動的例項接收到。具體實現如下:

@RunWith(SpringRunner.class)
@EnableBinding(value = {ExampleApplicationTests.ExampleBinder.class})
public class ExampleApplicationTests {

    @Autowired
    private ExampleBinder exampleBinder;

    @Test
    public void exampleBinderTester() {
        exampleBinder.output().send(MessageBuilder.withPayload("Produce a message from : http://blog.didispace.com").build());
    }

    public interface ExampleBinder {

        String NAME = "example-topic";

        @Output(NAME)
        MessageChannel output();

    }

}

啟動上述測試用例之後,可以發現之前啟動的兩個例項都收到的訊息,並在日誌中列印了:Received: Produce a message from : http://blog.didispace.com。訊息重複消費的問題成功重現!

使用消費組解決問題

如何解決上述訊息重複消費的問題呢?我們只需要在配置檔案中增加如下配置即可:

spring.cloud.stream.bindings.example-topic.group=aaa

當我們指定了某個繫結所指向的消費組之後,往當前主題傳送的訊息在每個訂閱消費組中,只會有一個訂閱者接收和消費,從而實現了對訊息的負載均衡。只所以之前會出現重複消費的問題,是由於預設情況下,任何訂閱都會產生一個匿名消費組,所以每個訂閱例項都會有自己的消費組,從而當有訊息傳送的時候,就形成了廣播的模式。

另外,需要注意上述配置中example-topic是在程式碼中@Output@Input中傳入的名字。

程式碼示例

本文示例讀者可以通過檢視下面倉庫的中的stream-consumer-group專案:

如果您對這些感興趣,歡迎star、follow、收藏、轉發給予支援!

以下專題教程也許您會有興趣

相關文章