rabbitmq发送确认和回退,消费确认ack机制,列信队列

这里以direct类型为例
application.yml

server:
  port: 8000
  #session-timeout: 1800

spring:
  # RabbitMQ 配置项,对应 RabbitProperties 配置类
  rabbitmq:   
    host: 127.0.0.1 # RabbitMQ 服务的地址
    port: 5672 # RabbitMQ 服务的端口
    username: guest # RabbitMQ 服务的账号
    password: guest # RabbitMQ 服务的密码
    publisher-returns: true              # 发送者开启 return 确认机制
    publisher-confirm-type: correlated   # 发送者开启 confirm 确认机制

pom.xml:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.7.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.rabbitmq</groupId>
    <artifactId>confirm</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>confirm</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>


         <!--// 新加的jar-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <version>2.3.7.RELEASE</version>
            <scope>compile</scope>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

常量类:

public interface RabbitmqConstant {

       String QUEUE_NAME = "QUEUE_DEMO_DIRECT";

       String EXCHANGE_NAME = "direct_exchange";

       String ROUTING_KEY = "ROUTING_KEY_01"; 

}

config类: 绑定队列,交换机和路由key

@Configuration
public class RabbitmqDirectConfig {

    @Bean("bootDirectExchange")
    public Exchange bootDirectExchange() {
        return 
 ExchangeBuilder.directExchange(RabbitmqConstant.EXCHANGE_NAME).durable(true).build();
    }

    @Bean("bootDirectQueue")
    public Queue bootDirectQueue() {
        return QueueBuilder.durable(RabbitmqConstant.QUEUE_NAME).build();
    }

    @Bean
    public Binding bindDirectQueueExchange(@Qualifier("bootDirectQueue") Queue queue, 
     @Qualifier("bootDirectExchange") Exchange exchange) {
        return BindingBuilder.bind(queue).to(exchange).with(RabbitmqConstant.ROUTING_KEY).noargs();
    }
}

监听类:当该列队有消息时自动消费

@Slf4j
@Component
public class RabbitmqListener {

    @RabbitListener(queues = RabbitmqConstant.QUEUE_NAME)
    public void ListenerQueue01(Message message, Channel channel) {

        System.out.println("mess====" + message.toString());
        log.info("[onMessage][线程编号:{} 消息内容:{}]", Thread.currentThread().getId(), message);
    } 
}

消息回调类:注意这一层是交换机有没有收到消息的回调


@Slf4j
@Component
public class ConfirmCallbackService implements RabbitTemplate.ConfirmCallback {

    /**
     *  交换机确认回调方法
     *  1.交换机接收到了,回调
     *
     * @param correlationData 保存回调消息的ID及相关消息
     * @param ack       交换机收到消息ack=true
     * @param cause     null
     *
     * 2.交换机接收失败,回调
     * @param correlationData 保存回调消息的ID及相关消息
     *@param ack       交换机没收到消息ack=false
     *@param cause     失败原因
     */
    @Override
    public void confirm(CorrelationData correlationData, boolean ack, String cause) {
        if (!ack) {
            log.error("消息发送异常!");
        } else {
            log.info("发送者已经收到确认,correlationData={} ,ack={}, cause={}", correlationData.getId(), ack, cause);
        }
    }
}

回退类: 注意这里是当不可到达目的地的时候才回调,例如路由key错了,无法把消息传递给队列

@Slf4j
@Component
public class ReturnCallbackService implements RabbitTemplate.ReturnCallback {


    /**
     *  当消息传递过程中不可达目的地时将消息返回给生产者
     *  只有不可达目的地的时候,才执行这里(是路由到队列那一层发生错误的时候执行这里)
     * @param message
     * @param replyCode
     * @param replyText
     * @param exchange
     * @param routingKey
     */
    @Override
    public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
        System.out.println("到了回退方法");
        log.info("returnedMessage ===> replyCode={} ,replyText={} ,exchange={} ,routingKey={}", replyCode, replyText, exchange, routingKey);
    }
}


controller类:


@Slf4j
@RestController
@RequestMapping("/confirm")
public class ConfirmController {

    @Resource
    private RabbitTemplate rabbitTemplate;

    @Autowired
    private ConfirmCallbackService confirmCallbackService;
    @Autowired
    private ReturnCallbackService returnCallbackService;

    @GetMapping("/sendConfirm/{msg}")
    public void sendConfirmMessage(@PathVariable("msg")String msg){
        /**声明回调的形参*/
        CorrelationData correlationData = new CorrelationData("1");
         rabbitTemplate.setConfirmCallback(confirmCallbackService);
        SimpleDateFormat format=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String mess=msg+"=="+format.format(new Date());
        rabbitTemplate.convertAndSend(RabbitmqConstant.EXCHANGE_NAME, 
       RabbitmqConstant.ROUTING_KEY,mess);
        log.info("发送信息为:" + mess);
    }

    @GetMapping("/sendReturn/{msg}")
    public void sendReturnMessage(@PathVariable("msg")String msg){
        /**声明回调的形参*/
        CorrelationData correlationData = new CorrelationData("1");
        rabbitTemplate.setMandatory(true);
        rabbitTemplate.setReturnCallback(returnCallbackService);
        SimpleDateFormat format=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String mess=msg+"=="+format.format(new Date());
        rabbitTemplate.convertAndSend(RabbitmqConstant.EXCHANGE_NAME, RabbitmqConstant.ROUTING_KEY+"222",
                mess);
        log.info("发送信息为:" + mess);
    }

}

还可以把消息确认类和回退类写一起:如下


@Component
@Slf4j
public class MyCallback implements RabbitTemplate.ConfirmCallback, RabbitTemplate.ReturnCallback {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @PostConstruct
    private void init(){
        rabbitTemplate.setConfirmCallback(this);
        rabbitTemplate.setReturnCallback(this);
    }

    /**
     *  交换机确认回调方法
     *  1.交换机接收到了,回调
     *
     * @param correlationData 保存回调消息的ID及相关消息
     * @param ack       交换机收到消息ack=true
     * @param cause     null
     *
     * 2.交换机接收失败,回调
     * @param correlationData 保存回调消息的ID及相关消息
     *@param ack       交换机没收到消息ack=false
     *@param cause     失败原因
     */
    @Override
    public void confirm(CorrelationData correlationData, boolean ack, String cause) {
        if (!ack) {
            log.error("消息发送异常!");
        } else {
            log.info("发送者已经收到确认,correlationData={} ,ack={}, cause={}", correlationData.getId(), ack, cause);
        }
    }

    /**
     *  当消息传递过程中不可达目的地时将消息返回给生产者
     *  只有不可达目的地的时候,才执行这里(是路由到队列那一层发生错误的时候执行这里)
     * @param message
     * @param replyCode
     * @param replyText
     * @param exchange
     * @param routingKey
     */
    @Override
    public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
        System.out.println("到了回退方法");
        log.info("returnedMessage ===> replyCode={} ,replyText={} ,exchange={} ,routingKey={}", replyCode, replyText, exchange, routingKey);
    }
}

controller类:


@Slf4j
@RestController
@RequestMapping("/confirm")
public class ConfirmController {

    @Resource
    private RabbitTemplate rabbitTemplate; 

    @GetMapping("/sendConfirm/{msg}")
    public void sendConfirmMessage(@PathVariable("msg")String msg){
        /**声明回调的形参*/
        CorrelationData correlationData = new CorrelationData("1");      
        SimpleDateFormat format=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String mess=msg+"=="+format.format(new Date());
        rabbitTemplate.convertAndSend(RabbitmqConstant.EXCHANGE_NAME, 
           RabbitmqConstant.ROUTING_KEY,
                mess);
        log.info("发送信息为:" + msg);
    }

    @GetMapping("/sendReturn/{msg}")
    public void sendReturnMessage(@PathVariable("msg")String msg){
        /**声明回调的形参*/
        CorrelationData correlationData = new CorrelationData("1");    
        SimpleDateFormat format=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String mess=msg+"=="+format.format(new Date());
        rabbitTemplate.convertAndSend(RabbitmqConstant.EXCHANGE_NAME, 
          RabbitmqConstant.ROUTING_KEY+"222",
                mess);
        log.info("发送信息为:" + msg);
    }

}

浏览器执行:http://127.0.0.1:8000/confirm/sendReturn/heheheeh222
                    http://127.0.0.1:8080/confirm/sendConfirm/heheheeh

注意:在springboot的测试类中不会执行回调和回退方法,要以这种接口请求方式
如果 在测试类就用内部类吧:

           rabbitTemplate.setConfirmCallback((correlation, ack, cause) -> {
                System.out.println("correlationData--->" + correlation);
              System.out.println(ack);
              if (ack) {
                  System.out.println("正常投递回复...");
                  //后续执行其他业务...
               } else {
                  System.out.println("投递异常....");
                  //后续记录等操作...
               }
            });

消费ack机制:

场景:在消费的时候,刚消费到,还没处理,结果进程挂了,比如重启了,RabbitMQ 认为你消费了,这数据就丢了。这个时候得用 RabbitMQ 提供的 ack 机制,就是你必须关闭 RabbitMQ 的自动 ack,可以通过一个 api 来调用就行,然后每次你自己代码里确保处理完的时候,再在程序里 ack 一把。这样的话,如果你还没处理完,不就没有 ack 了?那 RabbitMQ 就认为你还没处理完,这个时候 RabbitMQ 会把这个消费分配给别的 consumer 去处理,消息是不会丢。
代码实现:
首先配置添加listener:

server:
  port: 8000
  #session-timeout: 1800

spring:
  # RabbitMQ 配置项,对应 RabbitProperties 配置类
  rabbitmq: 
    host: 127.0.0.1 # RabbitMQ 服务的地址
    port: 5672 # RabbitMQ 服务的端口
    username: guest # RabbitMQ 服务的账号
    password: guest # RabbitMQ 服务的密码
    publisher-returns: true              # 发送者开启 return 确认机制
    publisher-confirm-type: correlated   # 发送者开启 confirm 确认机制
    # 设置消费端手动 ack
    listener:
      simple:
        default-requeue-rejected: false  #false不丢弃时需要写相应代码将该消息加入死信队列
        acknowledge-mode: manual
        # 是否支持重试
        retry:
          enabled: true

监听队列:

 @RabbitListener(queues = RabbitmqConstant.QUEUE_NAME)
    public void ListenerQueue01(String msg,Channel channel,Message message) throws IOException {
        long deliveryTag = message.getMessageProperties().getDeliveryTag();
          try{
              System.out.println("收到消息=="+msg);
              double a=1/0;
              channel.basicAck(deliveryTag,false);
          }catch (Exception e){
              if(message.getMessageProperties().getRedelivered()){
                  log.error("消息已重复处理失败,拒绝再次接收。。");
                  channel.basicReject(deliveryTag,false);
              }else{
                 // 第一个参数 deliveryTag:该消息的index
                // 第二个参数 multiple:是否批量.true:将一次性ack所有小于deliveryTag的消息。
               // 第三个参数 requeue:true则重新入队列,否则丢弃或者进入死信队列

                   log.error("重新入队,发送到正常队列。。");
                   channel.basicNack(deliveryTag,true,true);

                 // log.error("不重新入队,发送到死信队列。。");
                // channel.basicNack(deliveryTag,true,false);
              }
          }
    }

------------- -----------------  注意这里我遇到一个bug --------------------------------------
因为该队列和交换机和路由key 都是从之前的demo搬过来的,之前的工程的idea已经关了,但是我在rabbitmq控制台看到队列QUEUE_DEMO_DIRECT 有两个消费者,我一开始也没在意,直到我测试这个ack,重新入队列时没有调用,然后我测试如果消费的话会不会到死信队列,我把监听这个队列消费去掉了,还是不到死信队列,然后我注意到rabbitmq控制台这个队列还有一个消费者,我就意识到可能那个工程虽然idea关了但可能还在消费,所以我就把队列换了个名字,就OK了
如果是重新到正常队列,那么该方法会一直执行,如果是发送到信队列,那该正常队列绑定的列信队列会收到消息。
正常队列绑定死信交换机实现:

public interface RabbitmqConstant {

        String QUEUE_NAME = "QUEUE_DEMO_DIRECT_2";

        String EXCHANGE_DIRECT_NAME = "direct_exchange_2";

        String ROUTING_KEY = "ROUTING_KEY_02";

        //死信交换机
        String EXCHANGE_DEAD_NAME= "deadletter_exchange";
        //死信队列
        String QUEUEA_DEAD_NAME_A = "QUEUE_DEAD_A";     

        //死信路由
        String DEAD_ROUTING_KEY_A = "dead_routingkey_a";
     
}

死信队列绑定:


@Configuration
public class RabbitmqDeadConfig {

    // 声明死信Exchange
    @Bean("deadLetterExchange")
    public DirectExchange deadLetterExchange() {
        return new DirectExchange(RabbitmqConstant.EXCHANGE_DEAD_NAME);
    }

    // 声明死信队列
    @Bean("deadLetterQueue")
    public Queue deadLetterQueue() {
        return new Queue(RabbitmqConstant.QUEUEA_DEAD_NAME_A,true);
    }


    // 声明死信队列绑定关系
    @Bean
    public Binding deadLetterBindingA(@Qualifier("deadLetterQueue") Queue queue, @Qualifier("deadLetterExchange") DirectExchange exchange) {
        return BindingBuilder.bind(queue).to(exchange).with(RabbitmqConstant.DEAD_ROUTING_KEY_A);
    }

正常队列和死信交换机和路由key的绑定:

  
@Bean("bootDirectQueue")
    public Queue bootDirectQueue() {
        Map<String, Object> args = new HashMap<>(2);
        // x-dead-letter-exchange 这里声明当前队列绑定的死信交换机
        args.put("x-dead-letter-exchange", RabbitmqConstant.EXCHANGE_DEAD_NAME);
        // x-dead-letter-routing-key 这里声明当前队列的死信路由key
        args.put("x-dead-letter-routing-key", RabbitmqConstant.DEAD_ROUTING_KEY_A);
        /**设置过期时间*/
       // args.put("x-message-ttl", 10000);

        return QueueBuilder.durable(RabbitmqConstant.QUEUE_NAME).withArguments(args).build();
    }

监听死信队列:

   //监听 死信队列A
    @RabbitListener(queues = RabbitmqConstant.QUEUEA_DEAD_NAME_A)
    public void  ListenDeadA(Message message,Channel channel) throws IOException {
        long deliveryTag = message.getMessageProperties().getDeliveryTag();
        SimpleDateFormat format=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String date=format.format(new Date());
        System.out.println("mess====" + message.toString());
        log.info("[onMessage][线程编号:{} 消息内容:{},当前时间:{}]", Thread.currentThread().getId(), message,date);
        
        channel.basicAck(deliveryTag,false);
    }

消息成为死信的三种情况:
1.队列消息长度到达限制
2.消费者拒接消费消息,basicNack/basicReject,并且不把消息重新入列,requeue=false
3.原队列存在消息过期设置,消息到达设置时间未被消费

demo:
1.队列长度到达限制是否到了信队列:设置x-max-length=5 

  @Bean("bootDirectQueue")
    public Queue bootDirectQueue() {
        Map<String, Object> args = new HashMap<>(2);
        // x-dead-letter-exchange 这里声明当前队列绑定的死信交换机
        args.put("x-dead-letter-exchange", RabbitmqConstant.EXCHANGE_DEAD_NAME);
        // x-dead-letter-routing-key 这里声明当前队列的死信路由key
        args.put("x-dead-letter-routing-key", RabbitmqConstant.DEAD_ROUTING_KEY_A);
        /**设置过期时间*/
        args.put("x-message-ttl", 10000);
         args.put("x-max-length", 5);

        return QueueBuilder.durable(RabbitmqConstant.QUEUE_NAME).withArguments(args).build();
    }

                   、、、、、、、  发现 问题 ,,,,,,,,,,

这个队列的设置从第一开始就要设置好,中途不能在改,不然会报错

 所以我重新加了一个队列(在config里的代码如下):

 //另外一个队列
    @Bean("directExchange")
    public Exchange directExchange(){
        return ExchangeBuilder.directExchange(RabbitmqConstant.EXCHANGE_MAX_NAME).durable(true).build();
    }

    @Bean("directQueue")
    public Queue directQueue(){
        Map<String, Object> args = new HashMap<>(2);
        // x-dead-letter-exchange 这里声明当前队列绑定的死信交换机
        args.put("x-dead-letter-exchange", RabbitmqConstant.EXCHANGE_DEAD_NAME);
        // x-dead-letter-routing-key 这里声明当前队列的死信路由key
        //RabbitmqConstant.DEAD_ROUTING_KEY_A之前已经绑定了,这里在绑定,超过了5个竟然不到该 
        //路由A绑定的队列A了,又重新加了一个DEAD_ROUTING_KEY_B,然后把队列手动删除在运行的
        args.put("x-dead-letter-routing-key", RabbitmqConstant.DEAD_ROUTING_KEY_B);
        /**设置过期时间*/
        args.put("x-message-ttl", 60000);
        args.put("x-max-length", 5);
        return QueueBuilder.durable(RabbitmqConstant.QUEUE_MAX_NAME).withArguments(args).build();
    }
    @Bean
    public Binding bindDirectQueueExchange2(@Qualifier("directQueue") Queue queue, @Qualifier("directExchange") Exchange exchange) {
        return BindingBuilder.bind(queue).to(exchange).with(RabbitmqConstant.ROUTING_MAX_NAME).noargs();
    }

常量类RabbitmqConstant新加代码:

         //死信交换机
        String EXCHANGE_DEAD_NAME= "deadletter_exchange";
        //死信队列
        String QUEUEA_DEAD_NAME_A = "QUEUE_DEAD_A";
        String QUEUEA_DEAD_NAME_B = "QUEUE_DEAD_B";
        //死信路由
        String DEAD_ROUTING_KEY_A = "dead_routingkey_a";
        String DEAD_ROUTING_KEY_B = "dead_routingkey_b";


        //新队列
        String QUEUE_MAX_NAME="queue-maxlength";
        String EXCHANGE_MAX_NAME="exchange-maxlength";
        String ROUTING_MAX_NAME="routing-maxlength";

 config代码如下:

  // 声明死信Exchange
    @Bean("deadLetterExchange")
    public DirectExchange deadLetterExchange() {
        return new DirectExchange(RabbitmqConstant.EXCHANGE_DEAD_NAME);
    }

    // 声明死信队列A
    @Bean("deadLetterQueueA")
    public Queue deadLetterQueueA() {
        return new Queue(RabbitmqConstant.QUEUEA_DEAD_NAME_A,true);
    }
  // 声明死信队列A绑定关系
    @Bean
    public Binding deadLetterBindingA(@Qualifier("deadLetterQueueA") Queue queue, 
   @Qualifier("deadLetterExchange") DirectExchange exchange) {
        return 
   BindingBuilder.bind(queue).to(exchange).with(RabbitmqConstant.DEAD_ROUTING_KEY_A);
    }

     //新加的队列和绑定,交换机还是用之前那个
    // 声明死信队列B
    @Bean("deadLetterQueueB")
    public Queue deadLetterQueueB() {
        return new Queue(RabbitmqConstant.QUEUEA_DEAD_NAME_B,true);
    }  

    // 声明死信队列B绑定关系
    @Bean
    public Binding deadLetterBindingB(@Qualifier("deadLetterQueueB") Queue queue, @Qualifier("deadLetterExchange") DirectExchange exchange) {
        return BindingBuilder.bind(queue).to(exchange).with(RabbitmqConstant.DEAD_ROUTING_KEY_B);
    }

测试如下发送了七条数据,queue-maxlength只有五条,另外两条到了死信队列:

2.消息过期没有被消费,消息会到死信队列,过了60秒后所有消息到了信队列:

3. 监听队列然后拒收:发送了7条消息,信队列里多了7条,因此成功了

   //监听正常队列 queue-maxlength  然后拒收,测试是否到死信队列
    @RabbitListener(queues = RabbitmqConstant.QUEUE_MAX_NAME)
    public void ListenerQueue02(String msg,Channel channel,Message message) throws IOException {
        long deliveryTag = message.getMessageProperties().getDeliveryTag();
            System.out.println("收到消息=="+msg);
            channel.basicReject(deliveryTag,false);
                log.error("不重新入队,发送到死信队列。。");
                // channel.basicNack(deliveryTag,true,false);
        SimpleDateFormat format=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String date=format.format(new Date());
        System.out.println("mess====" + message.toString());
        log.info("[onMessage][线程编号:{} 消息内容:{},当前时间:{}]", Thread.currentThread().getId(), message,date);
    }

消费者限流:
1.确保ack机制为手动确认
2.listener-container配置属性 perfetch=1表示消费端每次从mq拉取一条消息来消费,直到手动确认消费完毕后,才会继续拉取下一条消息
代码实现:   先在配置类里加上prefetch=2 代表每次消费2条数据

server:
  port: 8000
  #session-timeout: 1800

spring:
  # RabbitMQ 配置项,对应 RabbitProperties 配置类
  rabbitmq:
    #host: 192.168.19.128 # RabbitMQ 服务的地址
    host: 127.0.0.1 # RabbitMQ 服务的地址
    port: 5672 # RabbitMQ 服务的端口
    username: guest # RabbitMQ 服务的账号
    password: guest # RabbitMQ 服务的密码
    publisher-returns: true              # 发送者开启 return 确认机制
    publisher-confirm-type: correlated   # 发送者开启 confirm 确认机制
    # 设置消费端手动 ack
    listener:
      simple:
        default-requeue-rejected: false  #false不丢弃时需要写相应代码将该消息加入死信队列
        acknowledge-mode: manual
        # 是否支持重试
        retry:
          enabled: true
        prefetch: 2   #每次消费者拉取二条消息进行消费

如下图,共发送7条消息,有2条待拉取,有5条待消费。

 TTL特性:
1.当消息到达存活时间后,还没有被消费,会被自动清除
2.可以对消息设置过期时间,也可以对整个队列设置过期 时间
3.如果设置了消息的过期时间也设置了队列的过期时间,它以时间短的为准
4.对队列设置TTL,队列过期后,会将队列所有消息全部移除
5.对消息设置TTL,消息过期后,只有消息在队列顶端,才会判断其是否过期(移除掉),就是说如果设置过期的消息在队列的中间,那到达过期时间后,在控制后台不会减1,因为它不在顶端。还没有被移除掉

延迟队列:
1. 用TTL + 死信队列   实现,比如淘宝下了订单过24小时还没有支付就取消订单,库存回滚,对订单是否支付的队列设置24小时的过期时间,到了这个时间没有消费的话,到死信队列,死信队列做逻辑判断,如果没支付就回滚库存和取消订单
2.安装插件实现延时队列
   2.1下载rabbitmq_delayed_message_exchange插件,下载前请确认自己的 RabbitMQ 版本,下载对应版本的插件。
官网地址 https://www.rabbitmq.com/community-plugins.htm

2.2  安装插件
 

#拿到CONTAINER ID
docker ps
#宿主机上传文件到容器
docker cp /Users/yz/Downloads/rabbitmq_delayed_message_exchange-3.8.0.ez 7bc42bc3bf07:/plugins
#进入容器
docker exec -it 7bc42bc3bf07 bash
#进入文件夹
cd plugins
#安装插件
rabbitmq-plugins enable rabbitmq_delayed_message_exchange
Enabling plugins on node rabbit@7bc42bc3bf07:
rabbitmq_delayed_message_exchange
The following plugins have been configured:
  rabbitmq_delayed_message_exchange
  rabbitmq_management
  rabbitmq_management_agent
  rabbitmq_web_dispatch
Applying plugin configuration to rabbit@7bc42bc3bf07...
Plugin configuration unchanged.
#退出
exit
#重启
docker restart 7bc42bc3bf07

容器启动成功之后,登录 RabbitMQ的管理界面,找到Exchanges Tab页。点击 add a new...,在 Type 里面查看是否有x-delayed-message选项,如果存在就代表插件安装成功。
 

 代码实现:

// RabbitmqConstant 添加
String DELAYED_QUEUE_NAME = "delay.queue.demo.delay.queue";
String DELAYED_EXCHANGE_NAME = "delay.queue.demo.delay.exchange";
String DELAYED_ROUTING_KEY = "delay.queue.demo.delay.routingkey";
// RabbitMqConfig 添加
@Bean
public Queue immediateQueue() {
    return new Queue(DELAYED_QUEUE_NAME);
}

@Bean
public CustomExchange customExchange() {
    Map<String, Object> args = new HashMap<>();
    args.put("x-delayed-type", "direct");
    return new CustomExchange(DELAYED_EXCHANGE_NAME, "x-delayed-message", true, false, args);
}

@Bean
public Binding bindingNotify(@Qualifier("immediateQueue") Queue queue,
                             @Qualifier("customExchange") CustomExchange customExchange) {
    return BindingBuilder.bind(queue).to(customExchange).with(DELAYED_ROUTING_KEY).noargs();
}
// 消息生产者
@GetMapping("delayMsg")
public void delayMsg2(String msg, Integer delayTime) {
    log.info("当前时间:{},收到请求,msg:{},delayTime:{}", new Date(), msg, delayTime);
    rabbitTemplate.convertAndSend(DELAYED_EXCHANGE_NAME, DELAYED_ROUTING_KEY, msg, a ->{
            a.getMessageProperties().setDelay(delayTime);
            return a;
        });
}
 
 
    @RabbitListener(queues = DELAYED_QUEUE_NAME)
    public void receiveD(Message message, Channel channel) throws IOException {
        String msg = new String(message.getBody());
        log.info("当前时间:{},延时队列收到消息:{}", new Date(), msg);
        channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
    }
 

这里和TTL方式有个很大的不同就是TTL存放消息在死信队列(delayqueue)里,二基于插件存放消息在延时交换机里(x-delayed-message exchange)。

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
RabbitMQ 中,消费者可以通过创建一个消息监听器来监听指定的队列,并在接收到消息后进行消息确认ACK)。 下面是一个示例代码,展示了如何使用 RabbitMQ Java 客户端库来实现消息监听和消息确认: ```java import com.rabbitmq.client.*; public class Consumer { private final static String QUEUE_NAME = "myQueue"; public static void main(String[] argv) throws Exception { // 创建连接工厂,并设置 RabbitMQ 服务器的连接信息 ConnectionFactory factory = new ConnectionFactory(); factory.setHost("localhost"); factory.setUsername("your_username"); factory.setPassword("your_password"); // 创建连接 Connection connection = factory.newConnection(); // 创建消息信道 Channel channel = connection.createChannel(); // 声明队列 channel.queueDeclare(QUEUE_NAME, false, false, false, null); // 创建消费者并设置消息处理逻辑 DefaultConsumer consumer = new DefaultConsumer(channel) { @Override public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { String message = new String(body, "UTF-8"); System.out.println("Received message: " + message); // 处理消息逻辑 // 手动发送消息确认 channel.basicAck(envelope.getDeliveryTag(), false); } }; // 设置消息确认模式为手动确认 channel.basicConsume(QUEUE_NAME, false, consumer); // 等待消息 while (true) { Thread.sleep(1000); } } } ``` 在上述代码中,首先创建了一个连接工厂,并设置 RabbitMQ 服务器的连接信息。然后创建连接和消息信道,声明了要消费队列。 接着创建了一个继承自 DefaultConsumer 的消费者对象,并重写了 handleDelivery 方法来处理接收到的消息。在处理消息的逻辑之后,使用 `channel.basicAck(envelope.getDeliveryTag(), false)` 手动发送消息确认。 最后,通过调用 `channel.basicConsume(QUEUE_NAME, false, consumer)` 来设置消费者的消息确认模式为手动确认,并开始等待消息的到来。 请根据你的实际需求进行相应的修改和优化。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值