Springboot + rabbitMQ实现延迟队列(生产者)

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;
    }


}

猜你喜欢

转载自blog.csdn.net/qq_37557563/article/details/106645389