Rabbitmq 通過死信隊列實現延遲消息發送

Rabbitmq 通過死信隊列實現延遲消息發送

設置消息的過期時間(TTL)

TTL, Time to Live 的簡稱, 即過期時間.

兩種方法設置 TTL

  1. 通過隊列屬性設置. 即隊列中所有的消息都有相同的過期時間. 在 channel.queueDeclare 方法中加入 x-message-ttl 參數實現, 單位是毫秒

  2. 對消息本身進行單獨設置. 即每條消息的 TTL 可以不同. 在 channel.basicPublish 方法中加入 expiration 屬性, 單位是毫秒

    總結: 如果兩種方法一起使用, 則以較小的那個 TTL 爲準. 消息過期後, 消費者無法再接收該消息, 就會變成死信(Dead Message). 這個特性可以實現延遲隊列功能

Java 代碼實現

給隊列設置 TTL

@Configuration
public class RabbitConfig implements ApplicationContextAware {
    
    private ApplicationContext applicationContext;    
    
    @PostConstruct
    public RabbitAdmin rabbitAdmin() {
        RabbitAdmin rabbitAdmin = applicationContext.getBean("rabbitAdmin", RabbitAdmin.class);
        rabbitAdmin.declareExchange(new TopicExchange("exchange.expiration"));
        Map<String, Object> argMap = new HashMap<>();
        argMap.put("x-message-ttl", 2000);// 2s 過期
        Queue queue = new Queue("queue.expiration", true, false, false, argMap);
        rabbitAdmin.declareQueue(queue);
        rabbitAdmin.declareBinding(
            BindingBuilder.bind(queue).to(new TopicExchange("exchange.expiration")).with("routingkey.expiration"));
        return rabbitAdmin;
    }
}

@Service
public class SendMQService {
    @Autowired
    private RabbitTemplate rabbitTemplate;
    
    public void sendMessage(String exchange, String routingKey, String msg) {
        rabbitTemplate.convertAndSend(exchange, routingKey, msg);
    }
}

給每一個消息單獨設置 TTL

@Configuration
public class RabbitConfig implements ApplicationContextAware {
    
    private ApplicationContext applicationContext;  
    
    @PostConstruct
    public RabbitAdmin rabbitAdmin() {
        RabbitAdmin rabbitAdmin = applicationContext.getBean("rabbitAdmin", RabbitAdmin.class);
        rabbitAdmin.declareExchange(new TopicExchange("exchange.expiration"));
        rabbitAdmin.declareQueue(new Queue("queue.expiration"));
        rabbitAdmin.declareBinding(BindingBuilder.bind(new Queue("queue.expiration"))
            .to(new TopicExchange("exchange.expiration")).with("routingkey.expiration"));
        return rabbitAdmin;
    }
}

@Service
public class SendMQService {
    @Autowired
    private RabbitTemplate rabbitTemplate;
    
    public void sendMessage(String exchange, String routingKey, String msg, Integer expirationTime) {
        rabbitTemplate.convertAndSend(exchange, routingKey, msg, message -> {
            message.getMessageProperties().setExpiration(expirationTime);
            return message;
        });
    }
}

死信隊列

DLX(Dead-Letter-Exchange), 可以稱之爲死信交換器, 也成死信信箱. 當消息在一個隊列中變成死信(dead message) 後, 會被重新發送到另外一個交換器中, 這個交換器就是 DLX. 綁定了 DLX 的隊列就是死信隊列. 說白了就是, 有兩個隊列, 一個隊列上的消息設置了過期時間, 但沒有消費者. 另一個隊列是普通隊列, 有消費者. 後者被稱爲死信隊列. 當前一個隊列消息過期後, Rabbitmq 會自動將過期消息轉發到死信隊列裏. 然後被死信隊列的消費者消費掉. 實現消息的延遲發送功能

延遲隊列

延遲隊列是爲了存放那些延遲執行的消息,待消息過期之後消費端從隊列裏拿出來執行

實現方法

通過在 channel.queueDeclare 方法中設置 x-dead-letter-exchange 參數爲這個隊列添加 DLX

Java 代碼

@Configuration
public class RabbitConfig implements ApplicationContextAware {
    
    private ApplicationContext applicationContext;    
    
    @PostConstruct
    public RabbitAdmin rabbitAdmin() {
        RabbitAdmin rabbitAdmin = applicationContext.getBean("rabbitAdmin", RabbitAdmin.class);
        // 死信隊列
        rabbitAdmin.declareExchange(new DirectExchange("exchange.dlx"));
        rabbitAdmin.declareQueue(new Queue("queue.dlx"));
        rabbitAdmin.declareBinding(
            BindingBuilder.bind(new Queue("queue.dlx")).to(new DirectExchange("exchange.dlx")).with("routingkey.dlx"));

        // 延遲隊列
        Map<String, Object> argMap = new HashMap<>(3);
        argMap.put("x-message-ttl", 2000);// 2s 過期
        argMap.put("x-dead-letter-exchange", "exchange.dlx");
        argMap.put("x-dead-letter-routing-key", "routingkey.dlx");
        rabbitAdmin.declareQueue(new Queue("queue.normal", true, false, false, argMap));
        rabbitAdmin.declareExchange(new TopicExchange("exchange.normal"));
        rabbitAdmin.declareBinding(BindingBuilder.bind(new Queue("queue.normal", true, false, false, argMap))
            .to(new TopicExchange("exchange.normal")).with("queue.normal"));
    }
}

缺點

使用死信隊列來實現消息的延遲發送. 如果是採用第一種方式, 即每個隊列設置相同的過期時間, 可以很好的實現消息的延遲發送功能. 如果採用第二種方式, 給每個消息設置不同的過期時間, 由於隊列先入先出的特性, 如果隊列頭的消息過期時間很長, 後面的消息過期時間很短, 會導致後面的消息過期後不能及時被消費掉

簡單的做法時, 使用 rabbitmq 的延遲插件: Rabbitmq 通過延遲插件實現延遲隊列

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