RabbitMQ(一)使用死信队列实现延迟队列消费

本文介绍如何利用RabbitMQ实现延迟消费,通过死信队列和延迟队列的概念,详细展示了如何配置和使用死信队列来实现消息的延迟处理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在使用rabbitmq实现延迟消费时,需要先明白什么是死信队列,什么是延迟队列。
rabbitmq并没有直接支持延迟队列,延迟队列是通过死信队列来实现的。

死信队列

DLX(Dead Letter Exchange),死信交换器。当队列中的消息被拒绝、或者过期会变成死信,死信可以被重新发布到另一个交换器,这个交换器就是DLX,与DLX绑定的队列称为死信队列。
造成死信的原因:

信息被拒绝
信息超时
超过了队列的最大长度

延迟队列

延迟队列存储的是延迟消息,延迟消息指的是,当消息被发发布出去之后,并不立即投递给消费者,而是在指定时间之后投递。如:在订单系统中,订单有30秒的付款时间,在订单超时之后在投递给消费者处理超时订单。
rabbitMq没有直接支持延迟队列,可以通过死信队列实现。在死信队列中,可以为普通交换器绑定多个消息队列,假设绑定过期时间为5分钟,10分钟和30分钟,3个消息队列,然后为每个消息队列设置DLX,为每个DLX关联一个死信队列。当消息过期之后,被转存到对应的死信队列中,然后投递给指定的消费者消费。

整体项目结构图

在这里插入图片描述

父pom文件

<!-- 定义公共资源版本 -->
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.5.RELEASE</version>
        <relativePath />
    </parent>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-parent</artifactId>
                <version>2.0.5.RELEASE</version>
            </dependency>
        </dependencies>
    </dependencyManagement>

死信队列生产者

pom文件

<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
    </dependencies>

application.yml文件

spring:
  rabbitmq:
    host: 127.0.0.1
    port: 5672
    username: admin
    password: admin
    #发布者确认
    publisher-confirms: true

server:
  port: 9041

rabbitmq:
  #死信队列(最终消费者会消费此队列)
  dlx:
    exchange: mayikt_order_dlx_exchange
    queue: mayikt_order_dlx_queue
    routingKey: dlx
  driver:
    exchange: exchange
    routingKey: routingkey
    queue: queue

rabbitmq配置

@Configuration
public class RabbitmqConfiguration {

    /**
     * exchange交换器名字
     */
    @Value("${rabbitmq.driver.exchange}")
    private String driverUpExchangeName;
    /**
     * 路由键名字
     */
    @Value("${rabbitmq.driver.routingkey}")
    private String driverUpRoutingKey;
    /**
     * queue
     */
    @Value("${rabbitmq.driver.queue}")
    private String driverUpQueue;
    /**
     * 死信交换机
     */
    @Value("${rabbitmq.dlx.exchange}")
    private String dlxExchange;
    /**
     * 死信队列
     */
    @Value("${rabbitmq.dlx.queue}")
    private String dlxQueue;
    /**
     * 死信路由
     */
    @Value("${rabbitmq.dlx.routingKey}")
    private String dlxRoutingKey;

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @PostConstruct
    public void initRabbitTemplate() {
        // 设置生产者消息确认
        rabbitTemplate.setConfirmCallback(new RabbitConfirmCallback());
    }


    //死信交换机
    @Bean
    public DirectExchange dlxExchange() {
        return new DirectExchange(dlxExchange);
    }


    //声明死信队列 (真正的队列)

    @Bean
    public Queue dlxQueue() {
        return new Queue(dlxQueue);
    }

    /**
     * 声明订单业务交换机(基本交换机)
     */
    @Bean
    public DirectExchange orderExchange() {
        return new DirectExchange(driverUpExchangeName);
    }

    /**
     * 声明订单队列(用于基本交换机和基本路由到死信队列的绑定)
     */
    @Bean
    public Queue orderQueue() {
        Map<String, Object> arguments = new HashMap<>(2);
        // 绑定我们的死信交换机
        arguments.put("x-dead-letter-exchange", dlxExchange);
        // 绑定我们的路由key
        arguments.put("x-dead-letter-routing-key", dlxRoutingKey);
        return new Queue(driverUpQueue, true, false, false, arguments);
    }

    /**
     * (创建基本交换机+基本路由 -> 死信队列 的绑定)
     */
    @Bean
    public Binding orderBinding() {
        return BindingBuilder.bind(orderQueue()).to(orderExchange()).with(driverUpRoutingKey);
    }

    /**
     * (死信交换机+死信路由->真正队列 的绑定)
     */
    @Bean
    public Binding binding() {
        return BindingBuilder.bind(dlxQueue()).to(dlxExchange()).with(dlxRoutingKey);
    }
}

生产者消息确认

@Slf4j
public class RabbitConfirmCallback implements RabbitTemplate.ConfirmCallback {

	@Override
	public void confirm(CorrelationData correlationData, boolean ack, String cause) {
		log.info("(start)生产者消息确认=========================");
		log.info("correlationData:[{}]", correlationData);
		log.info("ack:[{}]", ack);
		log.info("cause:[{}]", cause);
		if (!ack) {
			log.info("消息可能未到达rabbitmq服务器");
		}
		log.info("(end)生产者消息确认=========================");
	}
}

controller

@RestController
@RequestMapping("/rabbitmq")
public class SendController {

    @Autowired
    private DriverRabbitmqServce driverRabbitmqServce;

    @PostMapping("/send")
    public void send() {
        driverRabbitmqServce.sendDriverMessage(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()) + " 发送了死信消息");
    }

}

service

@Slf4j
@Service
public class DriverRabbitmqServce {

    /**
     * exchange交换器名字
     */
    @Value("${rabbitmq.driver.exchange}")
    private String driverUpExchangeName;
    /**
     * 路由键名字
     */
    @Value("${rabbitmq.driver.routingkey}")
    private String driverUpRoutingKey;

    @Autowired
    private AmqpTemplate amqpTemplate;

    //处理待发送消息()
    private MessagePostProcessor messagePostProcessor() {
        return new MessagePostProcessor() {
            @Override
            public Message postProcessMessage(Message message) throws AmqpException {
                //设置有效期 毫秒单位 后过期
                message.getMessageProperties().setExpiration("10000");
                return message;
            }
        };
    }

    public void sendDriverMessage(String driverNumber) {
        //向消息队列发送消息
        //参数一 : 交换器   参数三 : 消息
        this.amqpTemplate.convertAndSend(driverUpExchangeName, driverUpRoutingKey, driverNumber, messagePostProcessor());
        log.info("向消息队列发送消息 {}", driverNumber);
    }
}

项目结构如图
在这里插入图片描述

死信队列消费者

pom文件

<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
    </dependencies>

application.yml

spring:
  rabbitmq:
    host: 127.0.0.1
    port: 5672
    username: admin
    password: admin
    listener:
      simple:
        #设置消费端手动 ack   none不确认  auto自动确认  manual手动确认
        acknowledge-mode: manual
        retry:
          #开启消费者重试机制(为false时关闭消费者重试(开启消息重新投递),这时消费端代码异常会一直重复收到消息)
          enabled: true
          #重试次数5(重新投递次数)
          max-attempts: 5
          #重试时间间隔
          initial-interval: 5000
        #重试次数超过上面的设置之后是否丢弃(消费者listener抛出异常,是否重回队列,默认true:重回队列, false为不重回队列(结合死信交换机)
#        default-requeue-rejected: true
        #消费之最大数量
        max-concurrency: 1

server:
  port: 9040

rabbitmq:
  dlx:
    name: mayikt_order_dlx_name
    exchange: mayikt_order_dlx_exchange
    queue: mayikt_order_dlx_queue
    routingKey: dlx

消费者

@Slf4j
@Component
@RabbitListener(
        //绑定队列
        bindings = @QueueBinding(
                //需要指定queue的名字
                value = @Queue(
                        //配置队列名称
                        value = "${rabbitmq.dlx.name}",
                        //是否是一个可删除的临时队列 --> 消息持久化
                        autoDelete = "false"
                ),
                //配置交换器
                exchange = @Exchange(
                        //指定交换器名称
                        value = "${rabbitmq.dlx.exchange}",
                        //指定具体的交换器类型,有常量类指定
                        type = ExchangeTypes.DIRECT
                ),
                //路由键
                key = "${rabbitmq.dlx.routingkey}"
        )
)
public class DriverListen {


    /**
     * 接受消息的方法,采用消息队列监听机制
     */
    @RabbitHandler
    public void pay(String msg, Message message, Channel channel) throws Exception {
        log.info("consumers 接受到了 rabbitmq 消息 {}", msg);
        //消费者手动ack机制
        channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
        /**
         * 下面代码测试异常重试机制
         */
//        try {
//            int i = 1 / 0;
//        } catch (Exception e) {
//            //如果不抛出异常,catch之后无法重试
//            throw new RuntimeException("我异常了");
//        }
    }
}

项目结构图
在这里插入图片描述

只需要修改配置文件即可使用,相关位置都标明了注解,易于上手

项目测试,启动消费者和生产者,调用生产者的controller接口即可。查看消费者的消费时间。

关注公众号
在这里插入图片描述

每周会更新干货知识

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值