一張圖帶你理解和實現RabbitMQ的延遲佇列功能

爆米花機槍手發表於2018-06-14

開頭

先熟悉下面會用到的一些名詞~

  • exchange: 交換機

  • routingkey: 路由key

  • queue: 佇列

exchange和queue是需要繫結在一起的,然後訊息傳送到exchange再由exchange通過routingkey傳送到對應的佇列中。

mq.png

(不是這張圖~~~)

exchange分四種

Default Exchange

這種是特殊的Direct Exchange,是rabbitmq內部預設的一個交換機。該交換機的name是空字串,所有queue都預設binding 到該交換機上。所有binding到該交換機上的queue,routing-key都和queue的name一樣。

注意: 這就是為什麼你直接建立一個queue也能正常的生產與消費,因為對應的exchange是RabbitMQ預設的,routingkey就是該佇列的名字

Topic Exchange

萬用字元交換機,exchange會把訊息傳送到一個或者多個滿足萬用字元規則的routing-key的queue。其中表號匹配一個word,#匹配多個word和路徑,路徑之間通過.隔開。如滿足a..c的routing-key有a.hello.c;滿足#.hello的routing-key有a.b.c.helo。

Fanout Exchange

扇形交換機,該交換機會把訊息傳送到所有binding到該交換機上的queue。這種是publisher/subcribe模式。用來做廣播最好。
所有該exchagne上指定的routing-key都會被ignore掉。

Header Exchange

設定header attribute引數型別的交換機。

簡單的瞭解之後,下面就是延遲佇列的實現方式

延遲佇列的實現

延遲分兩種

  • 在msg上設定過期時間
  • 在佇列上設定過期時間

一定要看懂這張圖!!!
WX20180613-233153@2x.png

如上圖建立三個exchange和三個佇列

@Bean
public DirectExchange delayExchange() {
    return new DirectExchange(DELAY_EXCHANGE_NAME);
}

@Bean
public DirectExchange processExchange() {
    return new DirectExchange(PROCESS_EXCHANGE_NAME);
}

@Bean
public DirectExchange delayQueueExchange() {
    return new DirectExchange(DELAY_QUEUE_EXCHANGE_NAME);
}

/**
 * 存放延遲訊息的佇列 最後將會轉發給exchange(實際消費佇列對應的)
 * @return
 */
@Bean
Queue delayQueue4Msg(){
    return QueueBuilder.durable(DELAY_QUEUE_MSG)
            .withArgument("x-dead-letter-exchange", PROCESS_EXCHANGE_NAME) 
            .withArgument("x-dead-letter-routing-key", ROUTING_KEY) 
            .build();
}

@Bean
public Queue processQueue() {
    return QueueBuilder.durable(PROCESS_QUEUE)
            .build();
}

/**
 * 存放訊息的延遲佇列 最後將會轉發給exchange(實際消費佇列對應的)
 * @return
 */
@Bean
public Queue delayQueue4Queue() {
    return QueueBuilder.durable(DELAY_QUEUE_NAME)
            .withArgument("x-dead-letter-exchange", PROCESS_EXCHANGE_NAME) // DLX
            .withArgument("x-dead-letter-routing-key", ROUTING_KEY) 
            .withArgument("x-message-ttl", 3000) // 設定佇列的過期時間 單位毫秒
            .build();
}

接下來將每個exchange和對應的mq繫結

@Bean
Binding delayBinding() {
    return BindingBuilder.bind(delayQueue4Msg())
            .to(delayExchange())
            .with(ROUTING_KEY);
}

@Bean
Binding queueBinding() {
    return BindingBuilder.bind(processQueue())
            .to(processExchange())
            .with(ROUTING_KEY);
}
@Bean
Binding delayQueueBind() {
    return BindingBuilder.bind(delayQueue4Queue())
            .to(delayQueueExchange())
            .with(ROUTING_KEY);
}

傳送訊息的方式

public void sendDelayMsg(Msg msg) {
    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    System.out.println(msg.getId() + " 延遲訊息傳送時間:" + sdf.format(new Date()));
    rabbitTemplate.convertAndSend(RabbitConfig.DELAY_EXCHANGE_NAME, "delay", msg, new MessagePostProcessor() {
        @Override
        public Message postProcessMessage(Message message) throws AmqpException {
            message.getMessageProperties().setExpiration(msg.getTtl() + "");
            return message;
        }
    });
}

public void sendDelayQueue(Msg msg) {
    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    System.out.println(msg.getId() + " 延遲佇列訊息傳送時間:" + sdf.format(new Date()));
    rabbitTemplate.convertAndSend(RabbitConfig.DELAY_QUEUE_EXCHANGE_NAME,"delay",  msg);
}

驗證結果

為每個訊息設定過期時間
延遲訊息.gif

為佇列設定過期時間
延遲佇列訊息.gif

如果你把設定了過期時間的訊息傳送到設定了過期時間的隊裡中的時候,以最短的時間為準~~

最後

其實我在實現的過程中也花了很長的時間,主要就是被exchange和queue搞亂掉了,最後索性自己畫了個圖,按照圖來一個一個建立與繫結。之後就很清晰很容易的實現了。

強調!!! 如果在開發的過程中發現exchange和queue繫結錯誤了,建議從管理介面將queue和exchange unbind或者刪除重新建立!

程式碼已上傳到Github上Here

CSDN:http://blog.csdn.net/qqhjqs?viewmode=list

部落格:http://blog.wangxc.club

簡書:https://www.jianshu.com/u/223a1314e818

Github:https://github.com/vector4wang

Gitee:https://gitee.com/backwxc

如果感覺有幫助的話,點個贊哦~

相關文章