RabbitMQ學習(二十一):使用消息有效期TTL和死信路由DLX實現消息延遲重試消費的限制

說明

在之前的一篇博文《springboot學習(十三):RabbitMQ的使用 實現消息延遲消費》中,我簡單介紹了使用Rabbitmq的消息有效期和死信路由的特性實現消息的延遲消費。在後續的使用中,希望通過這兩種特性實現針對消息自定義過期時間實現延遲消費,但是發現Rabbitmq並不支持。本篇博文將對使用中出現的問題進行總結記錄。

正文

在之前延遲消費一文的示例代碼中,每個消息都設置了5s的有效期,在相同的過期時間時,消息的延遲消費可以正常實現。但是,在爲每個消息設置不同的有效期時,會出現消息已過期卻未被消費的情況。將之前的示例做以下修改:

生產者

生產者在發送消息時,根據方法參數設置消息體中有效期livetime的值

public void sendEmail(String livetime) {
    JSONObject msg = new JSONObject();
    String message = "this is a email";
    msg.put("body", message);
    msg.put("livetime", livetime);
    String routingKey = "email";
    rabbitTemplate.convertAndSend(proExchange.getName(), routingKey, msg.toJSONString());
    System.out.println("send successfully");
}

消費者

消費者根據消息體中livetime參數值設置消息的有效期

@RabbitListener(queues = {"proQueue"})
public void receiveEmail(String email) throws UnsupportedEncodingException {
    System.out.println("receive a email " + LocalDateTime.now().toString() + " " + email);
    JSONObject msg = JSON.parseObject(email);
    String livetime = msg.getString("livetime");
    String body = msg.getString("body");
    String routingKey = "email";
    MessageProperties messageProperties = getMessageProperties(livetime);
    if (body.indexOf("try") < 0) {
        JSONObject retry = new JSONObject();
        retry.put("body", body + " try again");
        retry.put("intervalTime", livetime);
        rabbitTemplate.convertAndSend(conExchange.getName(), routingKey, new Message((retry.toJSONString()).getBytes("utf-8"), messageProperties));
    }
}

測試結果

在這裏插入圖片描述
在這裏插入圖片描述
可以看到,當有效期短的消息先於有效期長的消息被髮送時,消息能夠正常的延遲重新消費,但是反之,有效期短的消息會被之前的消息阻塞而不能得到及時的死信路由。

原因

這是因爲在Rabbitmq中隊列是消息的一個有序集合,消息按照FIFO的原則進行消費,在之前的一篇博文《RabbitMQ學習(十二): 隊列和消息的有效期 (Queue and Message TTL)》中的注意事項中也提到,只有當過期消息到達隊列頭部時,過期消息纔會被真正的丟棄或死信路由。

在官方文檔中,對於消息的順序性也有說明,一般情況下隊列中的消息會按照FIFO的原則進行消費,但在優先級隊列,分片隊列或使用其他特性時,這種原則會被打破。

Message Ordering
Queues in RabbitMQ are ordered collections of messages. Messages are enqueued and dequeued (consumed) in the FIFO manner, although priority queues, sharded queues and other features may affect this.

結論

Rabbitmq中隊列是一個FIFO類型的隊列,消息在設置相同有效期時,能夠按照目標實現延遲消息。但是在根據條件爲消息設置不同的有效期時,因爲消息只有在隊列頭部時,纔會被死信路由,所以消息無法實現期望的及時準確的延遲消費。

那麼還能使用RabbitMQ實現複雜的延遲消費的需求嗎?
答案是可以,可以通過RabbitMQ的rabbitmq_delayed_message_exchange插件來設置每個消息的延遲時間。具體操作可以閱讀這篇博文《解決死信隊列消息過期非異步問題,RabbitMQ 延時消息更優解——插件大法(Docker版)》


參考資料:
https://www.rabbitmq.com/queues.html#message-ordering
https://www.skypyb.com/2020/01/jishu/1206/

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章