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);
        }
    }
}
相关推荐
gQ85v10Db6 小时前
Redis分布式锁进阶第十八篇:本地缓存+分布式锁双锁架构 + 高并发削峰兜底 + 极致性能无损优化实战
redis·分布式·缓存
小江的记录本6 小时前
【Kafka核心】Kafka高性能的四大核心支柱:零拷贝、批量发送、页缓存、压缩
java·数据库·分布式·后端·缓存·kafka·rabbitmq
gQ85v10Db7 小时前
Redis分布式锁进阶第十四篇:全系列终局架构复盘 + 锁体系统一规范 + 线上全年零事故收官方案
redis·分布式·架构
KmSH8umpK7 小时前
Redis分布式锁进阶第十二篇
数据库·redis·分布式
gQ85v10Db7 小时前
Redis分布式锁进阶第十六篇:番外高阶避坑篇 + 隐性埋点锁故障深挖 + 疑难杂症终极兜底方案
数据库·redis·分布式
KmSH8umpK8 小时前
Redis分布式锁从原生手写到Redisson高阶落地,附线上死锁复盘优化方案进阶第九篇
数据库·redis·分布式
gQ85v10Db8 小时前
Redis分布式锁进阶第十五篇:全系列终极收官复盘 + 全站锁规范归档 + 生产零故障长期运维兜底总方案
运维·redis·分布式
_F_y9 小时前
仿RabbitMQ实现消息队列-服务端核心模块实现(5)
分布式·rabbitmq
Lyyaoo.9 小时前
Redis实现分布式锁
数据库·redis·分布式
KmSH8umpK20 小时前
Redis分布式锁从原生手写到Redisson高阶落地,附线上死锁复盘优化方案进阶第八篇
数据库·redis·分布式