SpringBoot项目中RabbitMq延迟队列的应用

前言

在业务中使用场景有非常多,比如订单超时未支付取消、用户注册几天未操作自动注销等等。这里应用到的是物品到期做一个提醒。RabbitMQ实现延时队列有两种方式,一是在队列里的TTL时间,这种如果第一个消息的延时时长很长,而第二个消息的延时时长很短,第二个消息并不会优先得到执行。所以推荐第二种插件方式实现,他的TTL时间是在交换机中

基于插件实现延迟队列

1、安装延迟插件

RabbitMQ插件官网下载地址

  • 选择rabbitmq_delayed_message_exchangeReleases
    在这里插入图片描述
  • 选择自己安装的mq与Erlang对应版本,下载ez后缀的文件rabbitmq_delayed_message_exchange-3.10.0.ez
    在这里插入图片描述
  • 将下载的压缩包放在RabbitMq的安装目录下的plgins目录,可以使用whereis rabbitmq查看安装路径
  • 执行启用插件命令
rabbitmq-plugins enable rabbitmq_delayed_message_exchange
  • 重启RabbitMq服务
rabbitmqctl stop
rabbitmq-server -detached
  • 安装结果验
    在这里插入图片描述

2、pom.xml引入依赖

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

3、application.yml配置

spring:
 rabbitmq:
    host: 地址
    port: 5672
    username: 用户名
    password: 密码

4、延迟队列配置代码

配置架构图
在这里插入图片描述

/**
 * 延迟队列配置
 *
 * @author 权仔
 */
@Configuration
public class DelayedQueueConfig {
    public static final String EXCHANGE_NAME = "goods.delay.exchange";
    /**
     * 到期的
     */
    public static final String EXPIRED_DELAY_QUEUE = "expired.delay.queue";
    public static final String EXPIRED_KEY = "expired";

    /**
     * 死信队列
     */
    public static final String DEAD_EXCHANGE_NAME = "goods.dead.exchange";
    public static final String EXPIRED_DEAD_QUEUE = "expired.dead.queue";
    public static final String EXPIRED_DEAD_KEY = "expired.dead";

    @Bean(EXCHANGE_NAME)
    public CustomExchange delayExchange() {
        // 插件形式实现延迟消息
        Map<String, Object> args = new HashMap<>();
        args.put("x-delayed-type", "direct");
        return new CustomExchange(EXCHANGE_NAME, "x-delayed-message", true, false, args);
    }

    @Bean(DEAD_EXCHANGE_NAME)
    public DirectExchange deadExchange() {
        return new DirectExchange(DEAD_EXCHANGE_NAME, true, false);
    }

    @Bean(EXPIRED_DELAY_QUEUE)
    public Queue expiredQueue() {
        return QueueBuilder.durable(EXPIRED_DELAY_QUEUE)
                .deadLetterExchange(DEAD_EXCHANGE_NAME)
                .deadLetterRoutingKey(EXPIRED_DEAD_KEY).build();
    }

    @Bean(EXPIRED_DEAD_QUEUE)
    public Queue expiredDeadQueue() {
        return QueueBuilder.durable(EXPIRED_DEAD_QUEUE).build();
    }

    @Bean
    public Binding bindingA(@Qualifier(EXPIRED_DELAY_QUEUE) Queue queue,
                            @Qualifier(EXCHANGE_NAME) CustomExchange exchange) {
        // 过期物品提醒队列与交换机绑定
        return BindingBuilder.bind(queue).to(exchange).with(EXPIRED_KEY).noargs();
    }

    @Bean
    public Binding bindingExpiredDead(@Qualifier(EXPIRED_DEAD_QUEUE) Queue queue,
                                      @Qualifier(DEAD_EXCHANGE_NAME) DirectExchange exchange) {
        // 过期死信队列与交换机绑定
        return BindingBuilder.bind(queue).to(exchange).with(EXPIRED_DEAD_KEY);
    }
}

5、生产者代码

/**
 * 延迟队列生产者
 *
 * @author 权仔
 */
@Slf4j
@Service
public class DelayedQueueProducer {
    @Resource
    private RabbitTemplate rabbitTemplate;

    /**
     * 发送到任务延迟队列
     *
     * @param message    消息
     * @param delayTime  延时时间,毫秒值
     * @param routingKey 队列类型 {@link DelayedQueueConfig}
     */
    public void sendTask(String message, Integer delayTime, String routingKey) {
        log.info("当前时间:{},发送一条时长:{} ms 信息给延迟队列:{},类型:{}", new Date(), delayTime, message, routingKey);
        // 发送消息对应config声明的交换机关系
        rabbitTemplate.convertAndSend(DelayedQueueConfig.EXCHANGE_NAME, routingKey,
                message, msg -> {
                    // 发送消息的时候  延迟时长  单位:ms
                    msg.getMessageProperties().setDelay(delayTime);
                    return msg;
                });
    }
}

6、消费者代码

/**
 * 延迟队列消费者
 *
 * @author 权仔
 */
@Slf4j
@Component
public class DelayedQueueConsumer {
    @Resource
    private IGoodsService goodsService;

    @RabbitListener(queues = DelayedQueueConfig.EXPIRED_DELAY_QUEUE)
    public void expired(Message message, Channel channel) throws IOException {
        try {
            String msg = new String(message.getBody());
            log.info("当前时间:{} ,过期队列收到消息 :{} ", new Date(), msg);
            Goods goods = goodsService.getById(msg);
            if (null == goods) {
                // 代表已经删除的物品
                channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
            } else {
                // TODO 业务处理
                if (send) {
                    channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
                } else {
                	// 业务处理失败,手动应答拒绝放回死信队列
                    channel.basicReject(message.getMessageProperties().getDeliveryTag(), false);
                }
            }
        } catch (Exception e) {
            log.error("过期队列消息处理异常", e);
            channel.basicReject(message.getMessageProperties().getDeliveryTag(), false);
        }
    }
    
    @RabbitListener(queues = DelayedQueueConfig.EXPIRED_DEAD_QUEUE)
    public void expiredDead(Message message, Channel channel) throws IOException {
        try {
            String msg = new String(message.getBody());
            log.info("当前时间:{} ,过期死信队列收到消息 :{} ", new Date(), msg);
            // 处理失败的消息,保存到数据库,根据业务处理
            saveDeadMsg(message, channel, "expired");
        } catch (Exception e) {
            log.error("死信队列过期消息处理异常", e);
            channel.basicReject(message.getMessageProperties().getDeliveryTag(), true);
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值