RabbitMQ高级特性整合SpringBoot注解版入门笔记

本篇的rabbitmq基本的高级特性整合SpringBoot注解版入门笔记包括:

  1. 消息的可靠投递
  2. Consumer Ack
  3. 消费端限流
  4. TTL
  5. 死信队列DLX
  6. 延迟队列
  7. 日志与监控
  8. 消息追踪

高级特性

消息的可靠投递

作为消息发送方希望杜绝任何消息丢失或者投递失败场景

rabbitmq 整个消息投递的路径为:producer—>rabbitmq broker—>exchange—>queue—>consumer

confirm确认模式:消息从 producer 到 exchange 则会返回一个 confirmCallback

returns回调模式:消息从 exchange–>queue 投递失败则会返回一个 returnCallback

➢ 设置ConnectionFactory的publisher-confirms="true" 开启 确认模式。

➢ 使用rabbitTemplate.setConfirmCallback设置回调函数。当消息发送到exchange后回调confirm方法。在方法中判断ack,如果为true,则发送成功,如果为false,则发送失败,需要处理。

➢ 设置ConnectionFactory的publisher-returns="true" 开启 退回模式。

➢ 使用rabbitTemplate.setReturnCallback设置退回函数,当消息从exchange路由到queue失败后,如果设置了rabbitTemplate.setMandatory(true)参数,则会将消息退回给producer。并执行回调函数returnedMessage。 

➢ 在RabbitMQ中也提供了事务机制,但是性能较差
使用channel下列方法,完成事务控制:
txSelect(), 用于将当前channel设置成transaction模式
txCommit(),用于提交事务
txRollback(),用于回滚事务
测试,confirm消息模式

测配置生产者文件application.yml中添加

spring:
  rabbitmq:
    publisher-confirm-type: simple   #确认模式
    publisher-returns: true          #回退模式

测试代码:

package com.example.test_producer;

import org.junit.jupiter.api.Test;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
class TestProducerApplicationTests {
     

    @Autowired
    RabbitTemplate rabbitTemplate;

    @Test
    void testDirectProducer1(){
     
        rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
     
            /**
             *
             * @param correlationData 相关配置信息
             * @param b 交换机 是否成功收到了消息。true 成功,false代表失败
             * @param s 失败原因
             */
            @Override
            public void confirm(CorrelationData correlationData, boolean b, String s) {
     
                System.out.println("confirm方法被执行了....");
                if(b){
     
                    System.out.println("接收成功消息" + s);
                }else{
     
                    System.out.println("接收失败消息" + s);
                }
            }
        });
        rabbitTemplate.convertAndSend("test_direct1","error","这是error的日志信息");
    }

    @Test
    void testDirectProducer2() {
     
        //1.设置交换机处理失败消息的模式
//        rabbitTemplate.setMandatory(true);
        //2.设置ReturnCallBack
        rabbitTemplate.setReturnCallback(new RabbitTemplate.ReturnCallback() {
     
            @Override
            public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
     
                System.out.println("return 执行了....");
                System.out.println(message);
                System.out.println(replyCode);
                System.out.println(replyText);
                System.out.println(exchange);
                System.out.println(routingKey);
                //处理
            }
        });
        rabbitTemplate.convertAndSend("test_direct", "info", "这是info的日志信息");
    }
}

Consumer Ack

ack指Acknowledge,确认。 表示消费端收到消息后的确认方式。

有三种确认方式:

​ • 自动确认:acknowledge=“none

​ • 手动确认:acknowledge=“manual

​ • 根据异常情况确认:acknowledge=“auto” (比较复杂,忽略)

其中自动确认是指,当消息一旦被Consumer接收到,则自动确认收到,并将相应 message 从 RabbitMQ 的

消息缓存中移除。但是在实际业务处理中,很可能消息接收到,业务处理出现异常,那么该消息就会丢失。

如果设置了手动确认方式,则需要在业务处理成功后,调用channel.basicAck()手动签收

如果出现异常,则在catch中调用channel.basicNack()方法,拒绝消息,让其自动重新发送消息

➢ 在rabbit:listener-container标签中设置acknowledge属性,设置ack方式 none:自动确认,manual:手动确认
➢ 如果在消费端没有出现异常,则调用channel.basicAck(deliveryTag,false);方法确认签收消息
➢ 如果出现异常,则在catch中调用 basicNack或 basicReject,拒绝消息,让MQ重新发送消息。

测试消费的配置文件application.yml

spring:
  rabbitmq:
    host: 192.168.96.133
    port: 5672
    username: hrd
    password: hrd
    virtual-host: /test
    listener:
      simple:
        acknowledge-mode: manual #设置消费者手动确认

测试消费者

package com.example.test_consumer.Listen;

import com.rabbitmq.client.Channel;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.Exchange;
import org.springframework.amqp.rabbit.annotation.Queue;
import org.springframework.amqp.rabbit.annotation.QueueBinding;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;


@Component
public class TestACKDirectConsumer {
     

    @RabbitListener(bindings = {
     
            @QueueBinding(
                    value = @Queue(name = "test_direct_queue1"),key = {
     "error"},
                    exchange = @Exchange(type = "direct",name = "test_direct")
            )})
    public void testDirectReceive1(Message message){
     
        System.out.println("1:"+new String(message.getBody()));
    }

    @RabbitListener(bindings = {
     
            @QueueBinding(
                    value = @Queue(name = "test_direct_queue2"),key = {
     "info"},
                    exchange = @Exchange(type = "direct",name = "test_direct")
            )})
    public void testDirectReceive2(Message message, Channel channel) throws Exception {
     
        long deliveryTag = message.getMessageProperties().getDeliveryTag();
        try {
     
            //1.接收转换消息
            System.out.println("2:"+new String(message.getBody()));
            //2. 处理业务逻辑
            System.out.println("处理业务逻辑...");
//            int i=1/0; //出错
            //3. 手动签收
            channel.basicAck(deliveryTag,true);
        } catch (Exception e) {
     
            e.printStackTrace();
            //4.拒绝签收
            //第三个参数:requeue:重回队列。如果设置为true,则消息重新回到queue,broker会重新发送该消息给消费端
            channel.basicNack(deliveryTag,true,true);
//            channel.basicReject(deliveryTag,true);
        }
    }
}

消费端限流

场景:请求瞬间增多,用户每秒5000个请求,系统从MQ中每秒从MQ中拉取1000个请求处理

在配置文件中,消费端的确认模式一定为手动确认,acknowledge=“manual

配置 prefetch属性设置消费端一次拉取多少消息

spring:
  rabbitmq:
    .....
    .....
    listener:
      simple:
        acknowledge-mode: manual #设置消费者手动确认
        prefetch: 1 #消费端一次处理1个请求

注意:在消费端,每次监听获取prefetch个消息,使用调用channel.basicAck()手动签收后才会再次获取prefetch个消息

TTL

➢TTL 全称 Time To Live(存活时间/过期时间)

➢当消息到达存活时间后,还没有被消费,会被自动清除。

RabbitMQ可以对消息设置过期时间,也可以对整个队列(Queue)设置过期时间

➢ 设置队列过期时间使用参数:x-message-ttl,单位:ms(毫秒),会对整个队列消息统一过期。

设置消息过期时间使用参数:expiration。单位:ms(毫秒),当该消息在队列头部时(消费时),会单独判断

这一消息是否过期。

如果两者都进行了设置,以时间短的为准。

浏览器后台管理员配置队列过期时间/消息过期时间
RabbitMQ高级特性整合SpringBoot注解版入门笔记_第1张图片
代码设置消息过期时间

 @Test
    void testTTL(){
     
        //消息后处理对象,设置一些消息的参数信息
        MessagePostProcessor messagePostProcessor = new MessagePostProcessor(){
     
            @Override
            public Message postProcessMessage(Message message) throws AmqpException {
     
//                1.设置message信息
                message.getMessageProperties().setExpiration("5000"); //消息的过期时间
//                2.返回信息
                return message;
            }
        };
        //可以携带一个匿名内部类当作参数
        //当该消息在队列头部消费时,会单独判断这一消息是否过期。所以在消费端不签收的情况下,5秒后已经还是剩下10条信息未被处理
        for (int i = 0; i < 10; i++) {
     
            if(i==5){
     
                rabbitTemplate.convertAndSend("test_direct", "info", "这是info的日志信息",messagePostProcessor);
            }
            else{
     
                rabbitTemplate.convertAndSend("test_direct", "info", "这是info的日志信息");
            }
        }
    }

死信队列DLX

死信队列,英文缩写:DLX 。Dead Letter Exchange(死信交换机),当消息成为Dead message后,可以

被重新发送到另一个交换机,这个交换机就是DLX。
RabbitMQ高级特性整合SpringBoot注解版入门笔记_第2张图片
消息成为死信的三种情况:

  1. 队列消息长度x-max-length到达限制;

  2. 消费者拒接消费消息,basicNack/basicReject,并且不把消息重新放入原目标队列,requeue=false;

  3. 原队列存在消息过期设置,消息到达超时时间x-message-ttl未被消费;

队列绑定死信交换机:

创建死信交换机和死信队列与创建普通的交换机和队列是一样的,只是在普通队列中添加设置参数

给队列设置参数: x-dead-letter-exchange 和 x-dead-letter-routing-key

配置DLX也可以浏览器后台管理员在管理时配置

设置交换机

RabbitMQ高级特性整合SpringBoot注解版入门笔记_第3张图片
设置队列
RabbitMQ高级特性整合SpringBoot注解版入门笔记_第4张图片
具体实现,比较麻烦,可以按照结构图配置

普通队列绑定死信交换机和绑定普通交换机

RabbitMQ高级特性整合SpringBoot注解版入门笔记_第5张图片

死信队列绑定死信交换机
RabbitMQ高级特性整合SpringBoot注解版入门笔记_第6张图片
测试生产者可以才有TTL设置消息过期时间为5秒,运行后在后台web可以观察到普通队列test_queue_dlx中的信息5秒后转发到死信队列queue_dlx中(为了保证信息发送成功,故此次测试代码中我添加了信息确认机制)(为了更加明显,建议不要开启消费者)

//测试生产者
@Test
void testDLX() {
     
    //消息后处理对象,设置一些消息的参数信息
    MessagePostProcessor messagePostProcessor = new MessagePostProcessor(){
     
        @Override
        public Message postProcessMessage(Message message) throws AmqpException {
     
            //                1.设置message信息
            message.getMessageProperties().setExpiration("5000"); //消息的过期时间
            //                2.返回信息
            return message;
        }
    };
    rabbitTemplate.convertAndSend("test_direct", "dlx.#", "这是dlx.test的测试信息",messagePostProcessor);
}

消费者端注解版开发,测试结果为控制台输出"死信交换机:这是dlx.test的测试信息"

//测试消费者
@Component
public class TestDLXLirectConsumer {
     

//    @RabbitListener(bindings = {
     
//            @QueueBinding(
//                    value = @Queue(name = "test_queue_dlx",arguments = {
     
//                            @Argument(name = "x-dead-letter-exchange",value = "test_exchange_dlx"),
//                            @Argument(name= "x-dead-letter-routing-key",value = "dlx.#"),
//                            @Argument(name = "x-message-ttl",value = "5000",type = "java.lang.Integer")}
//                    ),key = {"dlx.#"},
//                    exchange = @Exchange(type = "direct",name = "test_direct")
//            )})
//    public void testDLXDirectReceive1(Message message, Channel channel)  {
     
//        System.out.println("普通交换机:"+new String(message.getBody()));
//        try {
     
//            Thread.sleep(5100);
//            channel.basicAck(message.getMessageProperties().getDeliveryTag(),true);
//        } catch (Exception e) {
     
//            e.printStackTrace();
//        }
//    }

    @RabbitListener(bindings = {
     
            @QueueBinding(
                    value = @Queue(name = "queue_dlx"),key = {
     "dlx.#"},
                    exchange = @Exchange(type = "direct",name = "test_exchange_dlx")
            )})
    public void testDLXDirectReceive2(Message message, Channel channel){
     
        System.out.println("死信交换机:"+new String(message.getBody()));
        try {
     
            channel.basicAck(message.getMessageProperties().getDeliveryTag(),true);
        } catch (IOException e) {
     
            e.printStackTrace();
        }
    }
}

延迟队列

延迟队列,即消息进入队列后不会立即被消费,只有到达指定时间后,才会被消费。

需求:

  1. 下单后,30分钟未支付,取消订单,回滚库存。

  2. 新用户注册成功7天后,发送短信问候。

实现方式:

  1. 定时器

  2. 延迟队列

很可惜,在RabbitMQ中并未提供延迟队列功能。但是可以使用:TTL+死信队列 组合实现延迟队列的效果
RabbitMQ高级特性整合SpringBoot注解版入门笔记_第7张图片

日志与监控

RabbitMQ默认日志存放路径: /var/log/rabbitmq/rabbit@主机名.log

RabbitMQ高级特性整合SpringBoot注解版入门笔记_第8张图片
日志包含了RabbitMQ的版本号、Erlang的版本号、RabbitMQ服务节点名称、cookie的hash值、RabbitMQ配置文件地址、内存限制、磁盘限制、默认账户guest的创建以及权限配置等等。

1.web管控台监控 http://192.168.96.133:15672/#/

2.Linux指令

# 查看队列
rabbitmqctl list_queues
查看exchanges
#rabbitmqctl list_exchanges
# 查看用户
rabbitmqctl list_users
# 查看连接
rabbitmqctl list_connections
# 查看消费者信息
rabbitmqctl list_consumers
# 查看环境变量
rabbitmqctl environment
# 查看未被确认的队列
rabbitmqctl list_queues name messages_unacknowledged
# 查看单个队列的内存使用
rabbitmqctl list_queues name memory
# 查看准备就绪的队列
rabbitmqctl list_queues name messages_ready

消息追踪

在使用任何消息中间件的过程中,难免会出现某条消息异常丢失的情况。

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

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命令

可以在Linux下使用指令“ rabbimq-plugins list ”查看自带的插件,其中前缀带有[e*]的选型代表已经启用的插件

# 查看插件
rabbimq-plugins list
# 启动插件
rabbimq-plugins enable 插件名
# 关闭插件
rabbimq-plugins disable 插件名

RabbitMQ高级特性整合SpringBoot注解版入门笔记_第9张图片
在web后台查看GUI界面操作。首次点击查看日志文件时,需要输入账号密码,数据格式Fromat可以选择Text或者Json,可以选择监听生产者或消费者

RabbitMQ高级特性整合SpringBoot注解版入门笔记_第10张图片
实质是系统会创建一个临时的队列进行保存日志信息,查看日志文件时从该队列取出进行消费

RabbitMQ高级特性整合SpringBoot注解版入门笔记_第11张图片

RabbitMQ应用问题和集群搭建待续

你可能感兴趣的:(中间件,JAVA,java,rabbitmq)