Springboot + rabbitMQ實現延遲隊列(生產者)

前言:在電商系統中,可能有這樣一個需求,訂單下單之後30分鐘後,如果用戶沒有付錢,則系統自動取消訂單。如果用常規的定時器定時去查詢,這會造成很大的消耗(頻繁訪問數據庫)。

這裏選擇RabbitMQ來實現類似的功能(使用隊列的TTL特性)

1.這種模式大概流程,我們需要將消息先發送到ttl延遲隊列內,當消息到達過期時間後會自動轉發到ttl隊列內配置的轉發Exchange以及RouteKey綁定的隊列內完成消息消費。

2.添加maven依賴

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-amqp</artifactId>
</dependency>ncy>

 

3.yml配置

spring:
  #rabbitmq消息隊列配置信息
  rabbitmq:
    host: localhost
    port: 5672
    username: root
    password: root
    #消息發送和接收確認
    publisher-confirms: true
    publisher-returns: true
    listener:
      direct:
        acknowledge-mode: manual
      simple:
        acknowledge-mode: manual
server:
  port: 9999

4.創建一個常量類,存放隊列和交換機名字--QueueContent

/**
 * @Auther: cjw
 * @Date: 2018/6/28 17:17
 * @Description:
 */
public class QueueContent {

    /**
     * 普通消息通知隊列名稱
     */
    public static final String MESSAGE_QUEUE_NAME="message.ordinary.queue";

    /**
     * ttl(延時)消息通知隊列名稱
     */
    public static final String MESSAGE_TTL_QUEUE_NAME="message.ttl.queue";

    /**
     * 普通交換機名稱
     */

    public static final String DIRECT_EXCHANGE_NAME="message.ordinary.exchange";


    /**
     * ttl(延時)交換機名稱
     */
    public static final String TOPIC_EXCHANGE_NAME="message.ttl.exchange";
}

5.建立一個隊列枚舉類-QueueEnum

@Getter
public enum QueueEnum {

    /**
     * 消息通知隊列
     */
    MESSAGE_QUEUE(QueueContent.DIRECT_EXCHANGE_NAME, QueueContent.MESSAGE_QUEUE_NAME, QueueContent.MESSAGE_QUEUE_NAME),
    /**
     * 消息通知ttl隊列
     */
    MESSAGE_TTL_QUEUE(QueueContent.TOPIC_EXCHANGE_NAME, QueueContent.MESSAGE_TTL_QUEUE_NAME, QueueContent.MESSAGE_TTL_QUEUE_NAME);
    /**
     * 交換名稱
     */
    private String exchange;
    /**
     * 隊列名稱
     */
    private String name;
    /**
     * 路由鍵
     */
    private String routeKey;

    QueueEnum(String exchange, String name, String routeKey) {
        this.exchange = exchange;
        this.name = name;
        this.routeKey = routeKey;
    }

    public String getExchange() {
        return exchange;
    }

    public void setExchange(String exchange) {
        this.exchange = exchange;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getRouteKey() {
        return routeKey;
    }

    public void setRouteKey(String routeKey) {
        this.routeKey = routeKey;
    }

}

6.建立隊列配置類-RabbitMqConfiguration

@Configuration
public class RabbitMqConfiguration {

    /**
     * 普通消息交換機配置
     *
     * @return
     */
    @Bean
    DirectExchange messageDirect() {
        return (DirectExchange) ExchangeBuilder
                .directExchange(QueueEnum.MESSAGE_QUEUE.getExchange())
                .durable(true)
                .build();
    }

    /**
     * 延時消息交換機配置
     *
     * @return
     */
    @Bean
    DirectExchange messageTtlDirect() {
        return (DirectExchange) ExchangeBuilder
                .directExchange(QueueEnum.MESSAGE_TTL_QUEUE.getExchange())
                .durable(true)
                .build();
    }

    /**
     * 普通消息隊列配置
     *
     * @return
     */
    @Bean
    public Queue messageQueue() {
        return new Queue(QueueEnum.MESSAGE_QUEUE.getName());
    }


    /**
     * TTL消息隊列配置
     *
     * @return
     */
    @Bean
    Queue messageTtlQueue() {
        return QueueBuilder
                .durable(QueueEnum.MESSAGE_TTL_QUEUE.getName())
                // 配置到期後轉發的交換
                .withArgument("x-dead-letter-exchange", QueueEnum.MESSAGE_QUEUE.getExchange())
                // 配置到期後轉發的路由鍵
                .withArgument("x-dead-letter-routing-key", QueueEnum.MESSAGE_QUEUE.getRouteKey())
                .build();
    }

    /**
     * 普通隊列和普通交換機的綁定-routekey
     *
     * @param messageDirect 消息中心交換配置
     * @param messageQueue  消息中心隊列
     * @return
     */
    @Bean
    Binding messageBinding(DirectExchange messageDirect, Queue messageQueue) {
        return BindingBuilder
                .bind(messageQueue)
                .to(messageDirect)
                .with(QueueEnum.MESSAGE_QUEUE.getRouteKey());
    }

    /**
     * ttl隊列和ttl交換機的綁定-routekey
     *
     * @param messageTtlQueue
     * @param messageTtlDirect
     * @return
     */
    @Bean
    public Binding messageTtlBinding(Queue messageTtlQueue, DirectExchange messageTtlDirect) {
        return BindingBuilder
                .bind(messageTtlQueue)
                .to(messageTtlDirect)
                .with(QueueEnum.MESSAGE_TTL_QUEUE.getRouteKey());
    }


}

上面就是隊列和交換機的綁定

1.聲明瞭普通消息通知隊列的相關ExchangeQueueBinding等配置,將message.ordinary.queue隊列通過路由鍵message.ordinary.queue綁定到了message.ordinary.exchange交換上。

2.聲明瞭延時消息通知隊列的相關ExchangeQueueBinding等配置,將message.ttl.queue隊列通過message.ttl.queue路由鍵綁定到了message.ttl.exchange交換上。

7.編寫消息發送---MessageProvider

@Component
public class MessageProvider implements RabbitTemplate.ConfirmCallback {

    static Logger logger = LoggerFactory.getLogger(MessageProvider.class);

    /**
     * RabbitMQ 模版消息實現類
     */
    protected RabbitTemplate rabbitTemplate;

    public MessageProvider(RabbitTemplate rabbitTemplate){
        this.rabbitTemplate = rabbitTemplate;
        this.rabbitTemplate.setMandatory(true);
        this.rabbitTemplate.setConfirmCallback(this);
    }

    private String msgPojoStr;

    /**
     * 發送延遲消息
     * @param messageContent
     */
    public void sendMessage(MessagePojo messageContent) {
        if (messageContent != null){
            //這裏用於消費者消費消息的時候處理具體業務
            if (StringUtils.isEmpty(messageContent.getClassName())){
                logger.error("處理業務的類名不能爲空");
                return;
            }

            messageContent.setMessageId(UUID.randomUUID().toString());
            messageContent.setCreateTime(DateUtil.datetoString(new Date()));
            String msg = JSON.toJSONString(messageContent);
            msgPojoStr = msg;
            logger.info("延遲:{}秒寫入消息隊列:{},消息內容:{}", messageContent.getDelay(), QueueEnum.MESSAGE_TTL_QUEUE.getRouteKey(), msg);
            // 執行發送消息到指定隊列
            CorrelationData correlationData = new CorrelationData(messageContent.getMessageId());
            rabbitTemplate.convertAndSend(QueueEnum.MESSAGE_TTL_QUEUE.getExchange(), QueueEnum.MESSAGE_TTL_QUEUE.getRouteKey(), msg, message -> {
                // 設置延遲毫秒值
                message.getMessageProperties().setExpiration(String.valueOf(messageContent.getDelay()*1000));
                return message;
            },correlationData);
        }else {
            logger.warn("消息內容爲空!!!!!");
        }

    }

    /**
     * 發送確認
     */
    @Override
    public void confirm(CorrelationData correlationData, boolean b, String s) {
        System.out.println(" 回調id:" + correlationData);
        if (b) {
            System.out.println(msgPojoStr+":消息發送成功");
        } else {
            logger.warn(msgPojoStr+":消息發送失敗:" + s);
        }
    }
}

 

8.消息實體--MessagePojo

@Data
public class MessagePojo implements Serializable {

    //定時過期時間(單位:秒)馬上消費,設置爲0
    private int delay;

    //處理類名(必填項)
    private String className;

    //消息參數
    private Map<String, Object> params;

    private String createTime;

    private String messageId;

    public MessagePojo() {

    }

    public MessagePojo(int delay, String className,Map<String, Object> params) {
        this.delay = delay;
        this.className = className;
        this.params = params;
    }


}

以上是消息的定義和發送,下一篇講消費者的處理 https://blog.csdn.net/u010096717/article/details/82149936

源碼地址:https://gitee.com/JanXs/springboot-rabbitMQ

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