rabbitmq 创建延迟队列

1、新建两个交换器(其实就是普通交换器)

2、新建延迟队列

3、将延迟任务交换器和延迟队列绑定

注意指定的路由,要和业务队列与业务交换器绑定路由相同

4、新建业务队列

5、将业务交换器和业务队列绑定

注意指定的路由,要和上面延迟队列与延迟任务交换器绑定路由相同

场景使用

yaml 复制代码
//sx.delay.exchange 延迟任务交换器
rabbitTemplate.send("sx.delay.exchange", "routing.sx.delay", message);
  1. 生产者发消息:发送到 sx.delay.exchange 交换机,路由键 routing.sx.delay
  2. 进入延迟队列:交换机根据绑定,把消息路由到sx-delay-queue 队列
  3. 等待过期:消息在sx-delay-queue 队列里躺到 TTL 时间到,变成死信
  4. 死信转发:RabbitMQ 读取队列的 x-dead-letter-exchange 配置,把消息自动发给 sx.change 交换机,同时用 routing.sx.delay作为路由键
  5. 进入业务队列:sx.change 交换机收到消息后,根据路由键 routing.sx.delay,把消息路由到真正的业务队列 pdt-billing-wb
  6. 消费者消费:你只需要监听 sx-yw-queue,就能拿到延迟后的消息

测试代码

controller层

yaml 复制代码
@GetMapping("/delay")
@ResponseBody
 public R delay() {
     delayQueueProducer.delayRetry("test");
     return R.ok();
 }

DelayQueueProducer类

yaml 复制代码
import lombok.Data;
import lombok.experimental.Accessors;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.support.CronSequenceGenerator;
import org.springframework.stereotype.Component;

import java.text.ParseException;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

@Slf4j
@Component
public class DelayQueueProducer {
    @Autowired
    private RabbitTemplate rabbitTemplate;
    //第一次进入延迟队列
    public void delayRetry(String msg) {
        DelayMQMessage delayMQMessage = new DelayMQMessage()
                .setDelay(10000) // 延迟时间毫秒
                .setContent(msg.getBytes());
        HashMap<String, Object> props = new HashMap<>();
        //这里可以设置一些需要判断消息的业务属性,比如请求次数,超时时间。
        props.put("PROP_TIMES", 0L);
        //设置超时时间
        props.put("PROP_ABORT", getAbort());
        delayMQMessage.setProps(props);
        send(delayMQMessage);
    }

	//再次进入延迟队列
    public String delayRetry(Message message, String content) {
        DelayMQMessage delayMQMessage = buildMessage(content);

        //判断次数是否超限
        long times = Long.valueOf(StringUtils.defaultString(getProp(message, "PROP_TIMES"), "0"));
        long limit = 3;
        if ((++times > limit)) {
            System.out.println("取消重试,重试次数超限");
            return "OVER_LIMIT";
        }

        //判断时间是否超限
        long abort = Long.valueOf(StringUtils.defaultString(getProp(message, "PROP_ABORT"), getAbort().toString()));
        if (System.currentTimeMillis() >= abort) {
            log.info("取消重试,重试时间截止!");
            return "OVER_TIMES";
        }

        HashMap<String, Object> props = new HashMap<>();
        props.put("PROP_TIMES", times);
        props.put("PROP_ABORT", abort);
        delayMQMessage.setProps(props);
        send(delayMQMessage);
        log.info("延迟重试,成功写入队列。");
        return StringUtils.EMPTY;
    }

    //发送队列消息
    public void send(DelayMQMessage delayMQMessage) {
        String expiration = getExpiration(delayMQMessage);
        MessageProperties properties = new MessageProperties();
        properties.setExpiration(expiration);
        if(!NumberUtils.isNullOrZero(delayMQMessage.getDelay())){
            properties.setDelay(delayMQMessage.getDelay());
        }
        for (Map.Entry<String, Object> prop : delayMQMessage.getProps().entrySet()) {
            properties.setHeader(prop.getKey(), prop.getValue());
        }
        Message message = new Message(delayMQMessage.getContent(), properties);
        log.info("发送延迟消息:{}", message);
        rabbitTemplate.send("sx.delay.exchange", "routing.sx.delay", message);
    }
		
	//获取过期时间
    private Long getAbort() {
        Date abort;
        try {
            abort = org.apache.commons.lang3.time.DateUtils.addDays(org.apache.commons.lang3.time.DateUtils.parseDate(org.apache.commons.lang3.time.DateFormatUtils.format(new Date(), "yyyy-MM-dd"), "yyyy-MM-dd"), 1);
        } catch (ParseException pe) {
            abort = org.apache.commons.lang3.time.DateUtils.addDays(new Date(), 1);
        }
        return abort.getTime();
    }

    private String getExpiration(DelayMQMessage delayMQMessage) {
        // 延迟时间
        int delay = delayMQMessage.getDelay();
        if (ObjectUtils.isNotEmpty(delay)) {
            return String.valueOf(delay);
        }

        final CronSequenceGenerator generator = new CronSequenceGenerator(delayMQMessage.getCron());
        return String.valueOf(generator.next(new Date()).getTime() - System.currentTimeMillis());
    }

    /**
     * 获取扩展属性值
     *
     * @param message 消息
     * @param prop    属性名
     * @return 属性值
     */
    public String getProp(Message message, String prop) {
        return message.getMessageProperties().getHeader(prop).toString();
    }

    @Data
    @Accessors(chain = true)
    class DelayMQMessage{
        /**
         * 延迟循环策略
         */
        private String cron;
        /*延迟等待时长(毫秒)*/
        private int delay;
        /**
         * 消息主体内容
         */
        private byte[] content;
        /**
         * 业务路由名称(用于转到指定的消费队列)
         */
        private String routing;

        private Map<String, Object> props;
    }

    private DelayMQMessage buildMessage(String msg) {
        return new DelayMQMessage()
                .setRouting("routing.sx.business")
                .setDelay(10000) // 默认1分钟
                .setContent(msg.getBytes());
    }
}

DelayQueueConsumer消费者

yaml 复制代码
@Slf4j
@Component
public class DelayQueueConsumer {
    @Autowired
    private DelayQueueProducer delayQueueProducer;

    @RabbitListener(queues = {"sx-yw-queue"}, ackMode = "MANUAL")
    public void consum2(String contentString, Channel channel, Message message) {
        long deliveryTag = message.getMessageProperties().getDeliveryTag();
        //处理业务
        log.info("延迟队列监听,{}",contentString);
		// 处理业务
        boolean result = false;  
        if(result){
            //业务成功
            this.doAck(channel, deliveryTag, contentString);
            return;
        }else{
            //处理失败, 再次放入延迟队列
            delayQueueProducer.delayRetry(message, contentString);
        }
        this.doAck(channel, deliveryTag, contentString);
    }

    private void doAck(Channel channel, long deliveryTag, String contentString) {
        try {
            channel.basicAck(deliveryTag, false);
        } catch (IOException e) {
            log.error("ACK确认失败!内容: {}", contentString, e);
        }
    }
}
相关推荐
Rick19933 小时前
RabbitMQ 死信队列(DLX)
分布式·rabbitmq
REDcker3 小时前
RabbitMQ系列02 - RabbitMQ 消息模型:Broker、交换器、队列与收发路径
分布式·rabbitmq·ruby
飞Link3 小时前
LangGraph SDK 全量技术手册:分布式 Agent 集群的远程调用与编排引擎
开发语言·分布式·python·数据挖掘
杰克尼3 小时前
redis(day06-多级缓存)
redis·分布式·缓存
SPC的存折3 小时前
分布式(加一键部署脚本)LNMP-Redis-Discuz5.0部署指南-小白详细版
linux·运维·服务器·数据库·redis·分布式·缓存
七夜zippoe3 小时前
DolphinDB集群部署:从单机到分布式
分布式·wpf·单机·dolphindb·分集群
不懂的浪漫13 小时前
mqtt-plus 架构解析(六):多 Broker 管理,如何让一个应用同时连接多个 MQTT 服务
spring boot·分布式·物联网·mqtt·架构
小夏子_riotous20 小时前
openstack的使用——5. Swift服务的基本使用
linux·运维·开发语言·分布式·云计算·openstack·swift
刘~浪地球1 天前
消息队列--Kafka 生产环境最佳实践
分布式·kafka·linq