《微服务实战》 第十五章 RabbitMQ 延迟队列

系列文章目录

第十六章 Spring cloud stream应用
第十五章 RabbitMQ 延迟队列
第十四章 RabbitMQ应用

在这里插入图片描述


前言

实际业务中,例如秒杀系统,秒杀商品成功会有截止时间,这时需要用到RabbitMQ延迟服务。

1、RabbitMQ延迟队列

1.1、方式1:RabbitMQ通过死信机制来实现延迟队列的功能

  • TTL ,即 Time-To-Live,存活时间,消息和队列都可以设置存活时间
  • Dead Letter,即死信,若给消息设置了存活时间,当超过存活时间后消息还没有被消费,则该消息变成了死信
  • Dead Letter Exchanges(DLX),即死信交换机
  • Dead Letter Routing Key (DLK),死信路由键
/***********************延迟队列*************************/
//创建立即消费队列
@Bean
public Queue immediateQueue(){
    return new Queue("immediateQueue");
}
//创建立即消费交换机
@Bean
public DirectExchange immediateExchange(){
    return new DirectExchange("immediateExchange");
}
@Bean
public Binding bindingImmediate(@Qualifier("immediateQueue") Queue queue,@Qualifier("immediateExchange") DirectExchange directExchange){
    return BindingBuilder.bind(queue).to(directExchange).with("immediateRoutingKey");
}

//创建延迟队列
@Bean
public Queue delayQueue(){
    Map<String,Object> params = new HashMap<>();
    //死信队列转发的死信转发到立即处理信息的交换机
    params.put("x-dead-letter-exchange","immediateExchange");
    //死信转化携带的routing-key
    params.put("x-dead-letter-routing-key","immediateRoutingKey");
    //设置消息过期时间,单位:毫秒
    params.put("x-message-ttl",60 * 1000);
    return new Queue("delayQueue",true,false,false,params);
}

@Bean
public DirectExchange delayExchange(){
    return new DirectExchange("delayExchange");
}

@Bean
public Binding bindingDelay(@Qualifier("delayQueue") Queue queue,@Qualifier("delayExchange") DirectExchange directExchange){
    return BindingBuilder.bind(queue).to(directExchange).with("delayRoutingKey");
}
@Test
public void sendDelay(){
    this.rabbitTemplate.convertAndSend("delayExchange","delayRoutingKey","Hello world topic");
}

1.2、方式二:安装延迟队列插件

1.2.1、安装延迟队列插件:

https://github.com/rabbitmq/rabbitmq-delayed-message-exchange/releases/download/3.11.1/rabbitmq_delayed_message_exchange-3.11.1.ez
下载解压,到plugins目录,执行以下的命令:

rabbitmq-plugins enable rabbitmq_delayed_message_exchange

/**************延迟队列一个单一queue*******************/
@Bean
public Queue delayNewQueue(){
    return new Queue("delayNewQueue");
}
@Bean
public CustomExchange delayNewExchange(){
    Map<String, Object> args = new HashMap<>();
    // 设置类型,可以为fanout、direct、topic
    args.put("x-delayed-type", "direct");
    return new CustomExchange("delayNewExchange","x-delayed-message", true,false,args);
}
@Bean
public Binding bindingNewDelay(@Qualifier("delayNewQueue") Queue queue,@Qualifier("delayNewExchange") CustomExchange customExchange){
    return BindingBuilder.bind(queue).to(customExchange).with("delayNewRoutingKey").noargs();
}
@Test
public void sendDelay() {
    //生产端写完了
    UserInfo userInfo = new UserInfo();
    userInfo.setPassword("13432432");
    userInfo.setUserAccount("tiger");
    this.rabbitTemplate.convertAndSend("delayNewExchange", "delayNewRoutingKey", userInfo
            , a -> {
                //单位毫秒
                a.getMessageProperties().setDelay(30000);
                return a;
            });
}

2、消息确认机制

消息确认分为两部分: 生产确认 和 消费确认。

生产确认: 生产者生产消息后,将消息发送到交换机,触发确认回调;交换机将消息转发到绑定队列,若失败则触发返回回调。
消费确认: 默认情况下消息被消费者从队列中获取后即发送确认,不管消费者处理消息时是否失败,不需要额外代码,但是不能保证消息被正确消费。我们增加手动确认,则需要代码中明确进行消息确认。

2.1、生产确认

@Bean
public RabbitTemplate getTemplate(ConnectionFactory connectionFactory){
    RabbitTemplate template = new RabbitTemplate(connectionFactory);
    //消息发送到交换器Exchange后触发回调
    template.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
        @Override
        public void confirm(CorrelationData correlationData, boolean ack, String cause) {
            //  可以进行消息入库操作
            log.info("消息唯一标识 correlationData = {}", correlationData);
            log.info("确认结果 ack = {}", ack);
            log.info("失败原因 cause = {}", cause);
        }
    });

    // 配置这个,下面的ReturnCallback 才会起作用
    template.setMandatory(true);
    // 如果消息从交换器发送到对应队列失败时触发(比如 根据发送消息时指定的routingKey找不到队列时会触发)
    template.setReturnsCallback(new RabbitTemplate.ReturnsCallback() {
        @Override
        public void returnedMessage(ReturnedMessage returnedMessage) {
            //  可以进行消息入库操作
            log.info("消息主体 message = {}", returnedMessage.getMessage());
            log.info("回复码 replyCode = {}", returnedMessage.getReplyCode());
            log.info("回复描述 replyText = {}", returnedMessage.getReplyText());
            log.info("交换机名字 exchange = {}", returnedMessage.getExchange());
            log.info("路由键 routingKey = {}", returnedMessage.getRoutingKey());

        }
    });
    return template;
}
spring:
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
  application:
    name: drp-user-service  #微服务名称
  datasource:
    username: root
    password: root
    url: jdbc:mysql://127.0.0.1:3306/drp
    driver-class-name: com.mysql.cj.jdbc.Driver
  rabbitmq:
    host: 127.0.0.1
    port: 5672
    username: tiger
    password: tiger
    virtual-host: tiger_vh
    # 确认消息已发送到交换机(Exchange)
    publisher-confirm-type: correlated
    # 确认消息已发送到队列
    publisher-returns: true
    listener:
      simple:
        acknowledge-mode: manual # 开启消息消费手动确认
        retry:
          enabled: true

2.2、消费确认

@RabbitHandler
public void process(UserInfo data, Message message, Channel channel){
    log.info("收到directQueue队列信息:" + data);
    long deliveryTag = message.getMessageProperties().getDeliveryTag();
    try {
        //成功消费确认
        channel.basicAck(deliveryTag,true);
        log.info("消费成功确认完毕。。。。。");
    } catch (IOException e) {
        log.error("确认消息时抛出异常 ", e);        
        // 重新确认,成功确认消息
        try {
            Thread.sleep(50);
            channel.basicAck(deliveryTag, true);
        } catch (IOException | InterruptedException e1) {
            log.error("确认消息时抛出异常 ", e);
            // 可以考虑入库
        }
    }catch (Exception e){
        log.error("业务处理失败", e);
        try {
            // 失败确认
            channel.basicNack(deliveryTag, false, false);
        } catch (IOException e1) {
            log.error("消息失败确认失败", e1);
        }
    }
}
  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
延迟队列在需要延时处理消息的场景下非常有用。使用RabbitMQ来实现延迟队列可以充分利用RabbitMQ的特性,如可靠发送和投递消息,以及死信队列来保证消息至少被消费一次,未被处理的消息也不会被丢弃。通过RabbitMQ集群的特性,可以解决单点故障的问题,即使某个节点挂掉,延迟队列仍然可用且消息不会丢失。\[1\] 在实现延迟队列的过程中,可以选择多种方式。比如利用Java的DelayQueue、Redis的zset、Quartz(定时器)或者Kafka的时间轮等。每种方式都有其适用的场景,需要根据具体需求来选择。\[1\] 关于RabbitMQ延迟队列实战,可以通过以下步骤来实现: 1. 安装Windows版Docker和RabbitMQ。 2. 使用Maven整合RabbitMQ,实现消息的生产和消费。 3. 使用RabbitMQ的发布确认和交换机确认来确保消息的可靠性。 4. 使用RabbitMQ的消息回报(队列确认)来处理未被消费的消息。 5. 可以考虑使用RabbitMQ的备份交换机和优先级队列来进一步增强延迟队列的功能。\[2\] 具体实现方式可以使用RabbitMQ提供的死信路由机制。当一个消息的时间戳到期时,如果还没有被消费,则会被转发到死信路由,消费者可以绑定到这个死信路由上来处理延迟消息。\[3\] #### 引用[.reference_title] - *1* [消息中间件 RabbitMQ延迟队列 详解&实战](https://blog.csdn.net/qq_52567278/article/details/124427476)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [RabbitMQ延迟队列实战](https://blog.csdn.net/m0_68681879/article/details/129547212)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] - *3* [【项目实战篇】基于 RabbitMQ 实现延迟队列](https://blog.csdn.net/qq_18244417/article/details/117050723)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

青花科技

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值