RabbitMQ简单使用

RabbitMQ简单使用

生产端

导入依赖:

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

配置文件:

spring:
  rabbitmq:
    host: 192.168.31.65
    port: 5672
    username: guest
    password: guest
    virtualHost: /rabbitmq
# 消息确认回调 交换机发的
    publisher-confirm-type: correlated
# 消息确认回调  queue发的
    publisher-returns: true

/**
 * rabbit 的配置  包含交换机、channel以及两者的绑定
 */

//@Configuration
public class RabbitConfig {


    public static final String QUEUE_INFORM_EMAIL = "queue_inform_email";
    public static final String QUEUE_INFORM_SMS = "queue_inform_sms";
    public static final String EXCHANGE_TOPICS_INFORM="exchange_topics_inform";
    public static final String ROUTINGKEY_EMAIL="email.#";
    public static final String ROUTINGKEY_SMS="sms.#";

    //声明交换机
    @Bean(EXCHANGE_TOPICS_INFORM)
    public Exchange EXCHANGE_TOPICS_INFORM(){
        //durable(true) 持久化,mq重启之后交换机还在
        return ExchangeBuilder.topicExchange(EXCHANGE_TOPICS_INFORM).durable(true).build();
    }

    //声明QUEUE_INFORM_EMAIL队列
    @Bean(QUEUE_INFORM_EMAIL)
    public Queue QUEUE_INFORM_EMAIL(){
        return new Queue(QUEUE_INFORM_EMAIL);
    }


    //ROUTINGKEY_EMAIL队列绑定交换机,指定routingKey
    @Bean
    public Binding BINDING_QUEUE_INFORM_EMAIL(@Qualifier(QUEUE_INFORM_EMAIL) Queue queue,
                                              @Qualifier(EXCHANGE_TOPICS_INFORM) Exchange exchange){
        return BindingBuilder.bind(queue).to(exchange).with(ROUTINGKEY_EMAIL).noargs();
    }
  
}

消费端:

导入依赖:

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

配置文件:

spring:
  rabbitmq:
    host: 192.168.31.65
    port: 5672
    username: guest
    password: guest
    virtualHost: /rabbitmq
    listener:
      simple:
        acknowledge-mode: manual #开启手动确认
      direct:
        consumers-per-queue: 3   #限流,每次3条
@Component
public class RabbitMqListener implements ChannelAwareMessageListener {

@RabbitListener(queues = "queue_inform_email")
public void ListenQueue(Message message){
    System.out.println("1");
    System.out.println(message);
	}
}

消息可靠投递:

1、生产端

        // confirmCallback 发送确认  交换机收到后发的
        rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
            @Override
            public void confirm(CorrelationData correlationData, boolean b, String s) {
//                System.out.println(correlationData);
//                System.out.println(b);
//                System.out.println(s);
            }
        });


        // returnCallback  发送确认  channel收到后发的
        rabbitTemplate.setReturnsCallback(new RabbitTemplate.ReturnsCallback() {
            @Override
            public void returnedMessage(ReturnedMessage returnedMessage) {
                System.out.println("==================");
                System.out.println(returnedMessage);
                /**
                 * ReturnedMessage [
                 *    message=(
                 *      Body:'你好呀????????????溜了溜了'
                 *      MessageProperties [
                 *              headers={}, contentType=text/plain, contentEncoding=UTF-8,
                 *              contentLength=0, receivedDeliveryMode=PERSISTENT, priority=0, deliveryTag=0]
                 *      ),
                 *    replyCode=312,
                 *    replyText=NO_ROUTE,
                 *    exchange=exchange_topics_inform,
                 *    routingKey=aaa.hhh
                 * ]
                 */

            }
        });

//发送
 rabbitTemplate.convertAndSend(RabbitConfig.EXCHANGE_TOPICS_INFORM,"email.hhh","你好呀????????????溜了溜了");

2、消费端

@Component
public class RabbitMqListener implements ChannelAwareMessageListener {



    /**
     * 消费端的确认
     */
    @Override
    @RabbitListener(queues = "queue_inform_email")
    public void onMessage(Message message, Channel channel) throws Exception {
            long deliveryTag = message.getMessageProperties().getDeliveryTag();
        try {
            System.out.println(message);
//            int a = 3/0;
            // 如果没出问题,就ACK
            channel.basicAck(deliveryTag,true);
        } catch (Exception e) {
            // 出了问题,NACK
            channel.basicNack(deliveryTag,true,true);
        }

    }
}

消费端限流

消费端xml:

spring:
  rabbitmq:
    host: 192.168.31.65
    port: 5672
    username: guest
    password: guest
    virtualHost: /rabbitmq
    listener:
      simple:
        acknowledge-mode: manual #开启手动确认
      direct:
        consumers-per-queue: 3   #限流,每次3条

RabbitMqListener:

@Component
public class RabbitMqListener implements ChannelAwareMessageListener {


    @Override
    @RabbitListener(queues = "queue_inform_email")
    public void onMessage(Message message, Channel channel) throws Exception {
        long deliveryTag = message.getMessageProperties().getDeliveryTag();
        try {
            System.out.println(message);
            // 如果没出问题,就ACK       开启限流后,这边ack了,才会消耗掉mq中的消息,不然数据就重回队列
            channel.basicAck(deliveryTag,true);
        } catch (Exception e) {
            // 出了问题,NACK
            channel.basicNack(deliveryTag,true,true);
        }

    }
}

TTL

TTL全称Time To Live(存活时间/过期时间)。
当消息到达存活时间后,还没有被消费,会被自动清除。
RabbitMQ可以对单个消息设置过期时间,也可以对整个队列(Queue)设置过期时间。

1、队列TTL

1.1、在后台添加:


\image-20220821192250579.png)

添加后:

1.2、代码实现

/**
 * rabbit 的配置  测试ttl
 */

@Configuration
public class RabbitConfig2 {

    //声明交换机
    @Bean("test_queue_ttl")
    public Exchange EXCHANGE_TOPICS_INFORM(){
        //durable(true) 持久化,mq重启之后交换机还在

        //声明各种模式的交换机
//        return ExchangeBuilder.fanoutExchange()
//        return ExchangeBuilder.directExchange()
        return ExchangeBuilder.topicExchange("test_queue_ttl").durable(true).build();
    }

    //声明ttl队列
    @Bean("ttlQueue")
    public Queue getQueue(){
        return QueueBuilder.durable("ttlQueue").ttl(10000).build();
    }

    //队列绑定交换机,指定routingKey
    @Bean
    public Binding BINDING_QUEUE_INFORM_EMAIL(@Qualifier("ttlQueue") Queue queue,
                                              @Qualifier("test_queue_ttl") Exchange exchange){
        return BindingBuilder.bind(queue).to(exchange).with("#.ttl").noargs();
    }

}

2、单个消息TTL

@Test
public void method(){

    // 消息后处理对象,设置消息的一些参数信息
    MessagePostProcessor messagePostProcessor = new MessagePostProcessor() {
        @Override
        public Message postProcessMessage(Message message) throws AmqpException {
            // 1、设置消息的信息
            message.getMessageProperties().setExpiration("5000"); //设置消息过期时间
            // 2、返回该消息
            return message;
        }
    };
    // 将上面的处理对象传入,就可以实现单个消息ttl
    rabbitTemplate.convertAndSend("test_queue_ttl","aa.ttl","hhhhhhhhhh",messagePostProcessor);
}

小总结

如果设置了消息的过期时间,也设置了队列的过期时间,它以时间短的为准。

队列过期后,会将队列所有消息全部移除。

消息过期后,只有消息在队列顶端,才会判断其是否过期(移除掉)

死信队列

死信队列,英文缩写:DLX。Dead Letter Exchange(死信交换机),当消息成为Dead message后,可以被重新发送到另一个交换机,这个交换机就是DLX。

消息成为死信的三种情况:

  1. 队列消息长度到达限制;
  2. 消费者拒接消费消息,basicNack/basicReject,并且不把消息重新放入原目标队列,requeue=false;
  3. 原队列存在消息过期设置,消息到达超时时间未被消费;
/**
 * rabbit 的配置  死信队列
 */

@Configuration
public class RabbitConfigDLX {

    /**
     * 死信队列:
     * 1.声明正常的队列(test_queue_dlx)和交换机(test_exchange_dlx)
     * 2.声明死信队列(queue_dLx)和死信交换机(exchange_dlx)
     * 3.正常队列绑定死信交换机
     * 设置两个参数:
     *  x-dead-letter-exchange :死信交换机名称
     *  x-dead-letter-routing-key:发送给死信交换机的routingkey
     *
     */

    //声明正常交换机
    @Bean("test_exchange_dlx")
    public Exchange EXCHANGE_formal(){
        return ExchangeBuilder.topicExchange("test_exchange_dlx").durable(true).build();
    }
    //声明正常队列
    @Bean("test_queue_dlx")
    public Queue getQueueFormal(){
        return QueueBuilder.durable("test_queue_dlx")
                // 设置过期时间和最大长度
                .ttl(10000).maxLength(10)
                // 正常队列绑定死信交换机
                .deadLetterExchange("exchange_dlx").deadLetterRoutingKey("dlx.aaa").build();
    }
    //正常队列绑定正常交换机
    @Bean
    public Binding BINDING_QUEUE_test(@Qualifier("test_queue_dlx") Queue queue,
                                              @Qualifier("test_exchange_dlx") Exchange exchange){
        return BindingBuilder.bind(queue).to(exchange).with("test.dlx.*").noargs();
    }





    //声明死信交换机
    @Bean("exchange_dlx")
    public Exchange EXCHANGE_DLX(){
        return ExchangeBuilder.topicExchange("exchange_dlx").durable(true).build();
    }
    //声明死信队列
    @Bean("queue_dlx")
    public Queue getQueue_DLX(){
        return QueueBuilder.durable("queue_dlx").ttl(15000).maxLength(200).build();
    }
    //死信队列绑定死信交换机
    @Bean
    public Binding BINDING_QUEUE_dlx(@Qualifier("queue_dlx") Queue queue,
                                              @Qualifier("exchange_dlx") Exchange exchange){
        return BindingBuilder.bind(queue).to(exchange).with("dlx.#").noargs();
    }

}

测试:

  /**
     * 发送测试死信消息:
     * 1.过期时间
     * 2.长度限制
     * 3.消息拒牧
     */
    @Test
    public void testDlx(){
        // 过期时间
//        rabbitTemplate.convertAndSend("test_exchange_dlx","test.dlx.aa","谁的父亲死了");

        //长度限制
        for (int i = 0; i < 20; i++) {
            rabbitTemplate.convertAndSend("test_exchange_dlx","test.dlx.aa","谁的父亲死了");
        }
        
        //消息拒牧
        rabbitTemplate.convertAndSend("test_exchange_dlx","test.dlx.aa","谁的父亲死了");

       

    }

消息拒收测试的消费端配置:

/**
 * 测试拒收后不返回队列
 */
@Override
@RabbitListener(queues = "test_queue_dlx")
public void onMessage(Message message, Channel channel) throws Exception {
    long deliveryTag = message.getMessageProperties().getDeliveryTag();
    try {
        int a = 3/0;
        channel.basicAck(deliveryTag,true);
    } catch (Exception e) {
        // 出了问题,NACK
        channel.basicNack(deliveryTag,true,false);
    }

}

死信队列小结:

  1. 死信交换机和死信队列和普通的没有区别
  2. 当消息成为死信后,如果该队列绑定了死信交换机,则消息会被死信交换机重新路由到死信队列
  3. 消息成为死信的三种情况:
    1. 队列消息长度到达限制;
    2. 消费者拒接消费消息,并且不重回队列;
    3. 原队列存在消息过期设置,消息到达超时时间未被消费;

延迟队列

TTL + DLX

/**
 * rabbit 的配置  延迟队列
 */

@Configuration
public class RabbitConfigOrder {

    /**
     * 延迟队列:
     * 1.定义正常交换机(order_exchange)和队列(order_queue)
     * 2.定义死信交换机(order_exchange_dlx〉和队列(order_queue_dLx)3.绑定,设置正常队列过期时间为36分钟
     */

    //声明正常交换机
    @Bean("order_exchange")
    public Exchange EXCHANGE_formal(){
        return ExchangeBuilder.topicExchange("order_exchange").durable(true).build();
    }
    //声明正常队列
    @Bean("order_queue")
    public Queue getQueueFormal(){
        return QueueBuilder.durable("order_queue")
                // 设置过期时间和最大长度
                .ttl(10000).maxLength(100)
                // 正常队列绑定死信交换机
                .deadLetterExchange("order_exchange_dlx").deadLetterRoutingKey("dlx.aaa").build();
    }
    //正常队列绑定正常交换机
    @Bean
    public Binding BINDING_QUEUE_test(@Qualifier("order_queue") Queue queue,
                                              @Qualifier("order_exchange") Exchange exchange){
        return BindingBuilder.bind(queue).to(exchange).with("order.dlx.*").noargs();
    }



    //声明死信交换机
    @Bean("order_exchange_dlx")
    public Exchange EXCHANGE_DLX(){
        return ExchangeBuilder.topicExchange("order_exchange_dlx").durable(true).build();
    }
    //声明死信队列
    @Bean("order_queue_dlx")
    public Queue getQueue_DLX(){
        return QueueBuilder.durable("order_queue_dlx").ttl(20000).maxLength(200).build();
    }
    //死信队列绑定死信交换机
    @Bean
    public Binding BINDING_QUEUE_dlx(@Qualifier("order_queue_dlx") Queue queue,
                                              @Qualifier("order_exchange_dlx") Exchange exchange){
        return BindingBuilder.bind(queue).to(exchange).with("dlx.#").noargs();
    }

}

测试:

/**
 * 发送测试延迟队列
 */
@Test
public void testOrder(){

    // 消息后处理对象,设置消息的一些参数信息
    MessagePostProcessor messagePostProcessor = new MessagePostProcessor() {
        @Override
        public Message postProcessMessage(Message message) throws AmqpException {
            // 1、设置消息的信息
            message.getMessageProperties().setExpiration("15000"); //设置消息过期时间
            // 2、返回该消息
            return message;
        }
    };

    for (int i = 0; i < 20; i++) {
        rabbitTemplate.convertAndSend("order_exchange","order.dlx.aa","谁的父亲死了",messagePostProcessor);
    }
}

小结:

  1. 延迟队列指消息进入队列后,可以被延迟一定时间,再进行消费。

  2. RabbitMQ没有提供延迟队列功能,但是可以使用:TTL + DLX来实现延迟队列效果。

消息追踪

​ 在使用任何消息中间件的过程中,难免会出现某条消息异常丢失的情况。对于RabbitMQ而言,可能是因为生产者或消费者与RabbitMQ断开了连接,而它们与RabbitMQ又采用了不同的确认机制;也有可能是因为交换器与队列之间不同的转发策略;甚至是交换器并没有与任何队列进行绑定,生产者又不感知或者没有采取相应的措施;另外RabbitMQ本身的集群策略也可能导致消息的丢失。这个时候就需要有一个较好的机制跟踪记录消息的投递过程,以此协助开发和运维人员进行问题的定位。

在RabbitMQ中可以使用Firehose和rabbitmq_tracing插件功能来实现消息追踪。

Firehose

​ firehose的机制是将生产者投递给rabbitmq的消息,rabbitmq投递给消费者的消息按照指定的格式发送到默认的exchange上。这个默认的exchange的名称为amq.rabbitmq.trace,它是一个topic型的exchange。发送到这个exchange上的消息的routing key为publish.exchangename和deliver.queuename。其中exchangename和queuename为实际exchange和queue的名称,分别对应生产者投递到exchange的消息,和消费者从queue上获取的消息。

注意:打开trace会影响消息写入功能,适当打开后请关闭。

rabbitmqctl trace_on:开启Firehose命令

rabbitmqctl trace_off:关闭Firehose命令

rabbitmq_tracing

RabbitMQ应用问题

集群

posted @ 2022-08-22 17:30  z-laoyao  阅读(169)  评论(0编辑  收藏  举报