RabbitMQ

85 阅读8分钟

消息中间件

image-20231003150826410

image-20231003150848351

一、概述

image-20231003151009899

image-20231003151101040

image-20231003151228181

image-20231003151326533

二、RabbitMQ概念

image-20231003151404726

image-20231003151528097

image-20231003151639673

image-20231003153022936

三、Docker安装RabbitMQ

image-20231003151952408

四、RabbitMQ运行机制

image-20231003152119524

1.Exchange 类型

direct:直接

fanout:扇出

topic:主题

image-20231003152214004

image-20231003152235017

image-20231003152837932

比如使用的是topic主题交换机,设置消息路由键 atguigu.news,如下图交换机路由键,四个队列都能匹配

image-20231025160421976

image-20231120192317051

image-20231120192501596

五、RabbitMQ整合

image-20231003152915608

1.引入 spring-boot-starter-amqp

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

2.application.yml配置

# RabbitMQ配置
spring.rabbitmq.host=IP地址
spring.rabbitmq.port=5672
# 虚拟主机配置
spring.rabbitmq.virtual-host=/
# 开启发送端消息抵达Broker确认
spring.rabbitmq.publisher-confirms=true
# 开启发送端消息抵达Queue确认
spring.rabbitmq.publisher-returns=true
# 只要消息抵达Queue,就会异步发送优先回调returnfirm
spring.rabbitmq.template.mandatory=true
# 手动ack消息,不使用默认的消费端确认
spring.rabbitmq.listener.simple.acknowledge-mode=manual

3.启动类

/**
 * 使用RabbitMQ
 * 1、引入amqp场景;RabbitAutoConfiguration就会自动生效
 * 2、给容器中自动配置了
 *      RabbitTemplate、AmqpAdmin、CachingConnectionFactory、RabbitMessagingTemplate;
 *      所有属性都是spring.rabbitmq
 *	    @ConfigurationProperties(prefix = "spring.rabbitmq")
 * 3、给配置文件中配置 spring.rabbitmq信息
 * 4、@EnableRabbit@EnableXxxxx;开启功能
 * 5、监听消息:使用@RabbitListener;必须有@EnableRabbit(如果只是发送消息可以不使用@EnableRabbit)
 *    @RabbitListener: 类 + 方法上(监听哪些队列)
 *    @RabbitHandler:标在方法上 (重载区分不同的消息:同一队列不同消息,不同队列的消息)
 */

@EnableRabbit
@SpringBootApplication
public class GulimallOrderApplication {

    public static void main(String[] args) {
        SpringApplication.run(GulimallOrderApplication.class, args);
    }

}

4.如何创建Exchange,Queue,Binding并发送消息

import org.springframework.amqp.core.*;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;

@Slf4j
@RunWith(SpringRunner.class)
@SpringBootTest
public class GulimallOrderApplicationTests {

    @Autowired
    private AmqpAdmin amqpAdmin;

    @Autowired
    private RabbitTemplate rabbitTemplate;
    
    /**
     * 1、如何创建Exchange、Queue、Binding
     *      1)、使用AmqpAdmin进行创建
     * 2、如何收发消息
     */
    @Test
    public void createExchange() {
	    //参数1(name):创建名为"hello-java-exchange"的路由,
        //参数2(durable):是否持久化
        //参数3(autoDelete):是否自动删除
        Exchange directExchange = new DirectExchange("hello-java-exchange",true,false);
        amqpAdmin.declareExchange(directExchange);
        log.info("Exchange[{}]创建成功:","hello-java-exchange");
    }



    @Test
    public void testCreateQueue() {
        //参数1(name):创建名为"hello-java-exchange"的队列,
        //参数2(durable):消息是否持久化
        //参数3(exclusive):是否排他,如果为true:指定连接才能消费消息
        //参数4(autoDelete):是否自动删除
        Queue queue = new Queue("hello-java-queue",true,false,false);
        amqpAdmin.declareQueue(queue);
        log.info("Queue[{}]创建成功:","hello-java-queue");
    }


    @Test
    public void createBinding() {
	   // String destination[目的地]
       // DestinationType destinationType[目的地类型,交换机和交换机绑定,交换机和队列绑定]
       //String exchange[交换机]
       //String routingKey[路由键]
       //Map<String,Object> arguments[自定义参数]
       //将exchange指定的交换机和destination目的地进行绑定,使用routingKey作为指定的路由键
        Binding binding = new Binding("hello-java-queue",
                Binding.DestinationType.QUEUE,
                "hello-java-exchange",
                "hello.java",
                null);
        amqpAdmin.declareBinding(binding);
        log.info("Binding[{}]创建成功:","hello-java-binding");

    }
 	@Test
    public void sendMessageTest() {
        OrderReturnReasonEntity reasonEntity = new OrderReturnReasonEntity();
        reasonEntity.setId(1L);
        reasonEntity.setCreateTime(new Date());
        reasonEntity.setName("reason");
        reasonEntity.setStatus(1);
        reasonEntity.setSort(2);
        String msg = "Hello World";
        //1、发送消息,如果发送的消息是个对象,会使用序列化机制,将对象写出去,对象必须实现Serializable接口

        //2、发送的对象类型的消息,可以是一个json
        rabbitTemplate.convertAndSend("hello-java-exchange","hello2.java",
                reasonEntity);
        log.info("消息发送完成:{}",reasonEntity);
    }
 

}

默认发送消息使用的Java序列化,如果想要使用json序列化,自定义配置类

@Configuration
public class MyRabbitConfig{
    @Bean
    public MessageConverter messageConverter(){
        return new Jackson2JsonMessageConverter();
    }
}

5.接受消息

/**
  * 1.queues:声明需要监听的队列
  * 2.T<发送的消息的类型> OrderReturnReasonEntity content;
  * 3.channel:当前传输数据的通道
  * 
  *Queue :可以很多人都来监听。只要收到消息,队列就会删除消息,而且只能有一个收到此消息
  *场景:
  * 1)、订单服务启动多个;同一个消息,只能有一个客户端收到(同一个订单服务启动多个,向同一队列中发送消息,发现多个服务轮询接受到消息)
  * 2)、只有一个消息处理完,方法运行结束,我们就可以接收到下一个消息(启动一个服务,在接受消息方法中加入线程睡眠,发现只有一个消息处理完,才会接受另一个消息)
*/
@RabbitListener(queues = {"hello-java-queue"})
public void revieveMessage(Message message, 实体类类型 content) {
    //拿到主体内容
    byte[] body = message.getBody();
    //拿到的消息头属性信息
    MessageProperties messageProperties = message.getMessageProperties();
    System.out.println("接受到的消息...内容" + message + "===内容:" + content);

}

接受不同类型的消息

public class RabbitController{
    @Autowite
    RabbitTemplate rabbitTemplate;

    @GetMapping("/sendMq")
    public String sendMq(){
        for(int i = 0 ;i < 10 ;i++){
            if(i % 2 == 0){
                OrderReturnReasonEntity reasonEntity = new OrderReturnReasonEntity();
                reasonEntity.setId(1L);
                reasonEntity.setCreateTime(new Date());
                reasonEntity.setName("reason");
                String msg = "Hello World";
                //1、发送消息,如果发送的消息是个对象,会使用序列化机制,将对象写出去,对象必须实现Serializable接口

                //2、发送的对象类型的消息,可以是一个json
                rabbitTemplate.convertAndSend("hello-java-exchange","hello.java",reasonEntity,new CorrelationData(UUID.randomUUID().toString()));
                log.info("消息发送完成:{}",reasonEntity);
            }else{
                OrderEntity entity = new OrderEntiy();
                entity.setOrderSn("1111");
                rabbitTemplate.convertAndSend("hello-java-exchange","hello.java", entity,new CorrelationData(UUID.randomUUID().toString()));
            }
        } 
    }
}
@RabbitListener(queues = {"hello-java-queue"})
public class OrderItemServiceImpl{
    @RabbitHandler
    public void revieveMessage(OrderReturnReasonEntity content) {
        System.out.println("接受到的消息...内容" + message + "===内容:" + content);

    }
    @RabbitHandler
    public void revieveMessage2(OrderEntity content) {
        System.out.println("接受到的消息...内容" + message + "===内容:" + content);
    }
}

六、RabbitMQ消息确认机制-可靠抵达

image-20231003232256770

可靠抵达-ConfirmCallback

image-20231003232430158

# 开启发送端消息抵达Broker确认
spring.rabbitmq.publisher-confirms=true
# 新版本更换为
spring.rabbitmq.publisher-confirm-type: correlated
@Configuration
public class MyRabbitMQConfig {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    /**
     * 定制RabbitTemplate
     * 1、服务收到消息就回调
     *          1、spring.rabbitmq.publisher-conforms=true
     *          2、设置确定回调
     */
    @PostConstruct
    public void initRabbitTemplate() {
        //设置确认回调
        rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
            /**
             * 1、只要消息抵达Broker就ack=true
             * @param correlationData 当前消息的唯一的关联数据(这个是消息的唯一id)
             * @param ack 消息是否成功收到
             * @param cause 失败的原因
             */
            @Override
            public void confirm(CorrelationData correlationData, boolean ack, String cause) {
                System.out.println("confirm...correlationData[" + correlationData + "]==>ack[" + ack + "]===>cause[" + cause + "]");
            }
        });
    }
}

可靠抵达-ReturnCallback

image-20231004000132268

# 开启发送端消息抵达Queue确认
spring.rabbitmq.publisher-returns=true
# 只要消息抵达Queue,就会异步发送优先回调returnfirm
spring.rabbitmq.template.mandatory=true
@Configuration
public class MyRabbitMQConfig {

    @Autowired
    private RabbitTemplate rabbitTemplate;
    
    /**
     * 定制RabbitTemplate
     * 1、服务收到消息就回调
     *          1、spring.rabbitmq.publisher-conforms=true
     *          2、设置确定回调
     * 2、消息正确抵达队列进行回调
     *          1、spring.rabbitmq.publisher-returns=true
     *          2、spring.rabbitmq.template.mandatory=true
     * 3、消费者确定(保证每个消息被正确消费,此时才可以broker删除这个消息)
     */
    @PostConstruct
    public void initRabbitTemplate() {
        //设置确认回调
        rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
            /**
             * 1、只要消息抵达Broker就ack=true
             * @param correlationData 当前消息的唯一的关联数据(这个是消息的唯一id)
             * @param ack 消息是否成功收到
             * @param cause 失败的原因
             */
            @Override
            public void confirm(CorrelationData correlationData, boolean ack, String cause) {
                System.out.println("confirm...correlationData[" + correlationData + "]==>ack[" + ack + "]===>cause[" + cause + "]");
            }
        });
        // 设置消息抵达队列的确定回调(当消息路由不到消息队列中时才会调用该方法)
        rabbitTemplate.setReturnCallback(new 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("message = " + message + ", replyCode = " + replyCode + ", replyText = " + replyText + ", exchange = " + exchange + ", routingKey = " + routingKey);
            }
        });
    }
}

可靠抵达-Ack消息确认机制

image-20231004100610435

# 手动ack消息,不使用默认的消费端确认
spring.rabbitmq.listener.simple.acknowledge-mode=manual
/**
     * 1.queues:声明需要监听的队列
     * 2.T<发送的消息的类型> OrderReturnReasonEntity content;
     * 3.channel:当前传输数据的通道
     *
     *Queue :可以很多人都来监听。只要收到消息,队列就会删除消息,而且只能有一个收到此消息
     *场景:
     * 1)、订单服务启动多个;同一个消息,只能有一个客户端收到(同一个订单服务启动多个,向同一队列中发送消息,发现多个服务轮询接受到消息)
     * 2)、只有一个消息处理完,方法运行结束,我们就可以接收到下一个消息(启动一个服务,在接受消息方法中加入线程睡眠,发现只有一个消息处理完,才会接受另一个消息)
     */
// @RabbitListener(queues = {"hello-java-queue"})
@RabbitHandler
public void revieveMessage(Message message,
                           实体类类型 content,
                           Channel channel) {
    //拿到主体内容
    byte[] body = message.getBody();
    //拿到的消息头属性信息
    MessageProperties messageProperties = message.getMessageProperties();
    System.out.println("接受到的消息...内容" + message + "===内容:" + content);
    // channel内按顺序自增的。
    long deliveryTag = message.getMessageProperties().getDeliveryTag();
    System.out.println("deliveryTag ==> " + deliveryTag);

    // 手动签收货物,非批量模式
    try {
        if (deliveryTag % 2 == 0){
            // 收货
            channel.basicAck(deliveryTag,false);
            System.out.println("签收了货物...."+deliveryTag);
        }else{
            // 退货 requeue=false丢弃 requeue=true 发回服务器,服务器重新入队。
            // long deliveryTag, boolean multiple, boolean requeue
            channel.basicNack(deliveryTag,false,true);
            // (long deliveryTag, boolean requeue)
            // channel.basicReject();
            System.out.println("没有签收货物...."+deliveryTag);
        }

    } catch (Exception e) {
        // 网络中断
        e.printStackTrace();
    }
}
    /**
     * 定制RabbitTemplate
     * 1、服务收到消息就回调
     *          1、spring.rabbitmq.publisher-conforms=true
     *          2、设置确定回调
     * 2、消息正确抵达队列进行回调
     *          1、spring.rabbitmq.publisher-returns=true
     *          2、spring.rabbitmq.template.mandatory=true
     * 3、消费者确定(保证每个消息被正确消费,此时才可以broker删除这个消息)
     *          spring.rabbitmq.listener.simple.acknowledge-mode=manual
     *          1、默认是自动确认的,只要消息接受到,客户端会自动确认,服务端就会移除这个消息
     *              问题:
     *                  我们收到很多消息,自动回复给服务器ack,只有一个消息处理成功,宕机了。发生消息丢失;
     *                   消费者手动确认模式。只要我们没有明确告诉MQ,货物被签收。没有Ack,
     *                   消息就一直是unacked状态。即使Consumer宕机。消息不会丢失,会重新变为Ready,下一次有新的consumer进来,就发给它
     *          2、如何签收
     *              channel.basicAck(deliveryTag,false);签收;业务成功完成就应该签收
     *              channel.basicNack(deliveryTag,false,true);拒签;业务失败,拒签
     */

七、RabbitMQ延时队列(实现定时任务)

延时队列场景

image-20231004213737061

定时任务的时效性问题

image-20231004213754190

image-20231004213831841

消息的TTL(Time To Live)

image-20231004214049914

Dead Letter Exchanges(DLX)死信路由

image-20231004214439977

image-20231004221255257

延时队列实现-1

image-20231004220359845

延时队列实现-2

image-20231004220423662

具体实现流程1

image-20231004220932599

具体实现流程2

image-20231004220950184

SpringBoot中使用延时队列

image-20231116212731021

八、如何保证消息可靠性

消息丢失

image-20231116213115871

消息重复

image-20231116213348117

消息积压

image-20231116213426337

实操

@Configuration
public class MyRabbitMQConfig {

   	//@Bean Binding,Queue,Exchange(并且有监听消息监听时自动创建(使用@RabbitListener(queue = "")))
    /**
     * 容器中的 Binding,Queue,Exchange都会自动创建(RabbitNQ没有的情况)
     * RabbitMQ只要有。@Bean声明属性发生变化也不会覆盖
     * 死信队列
     * @return
     */
    @Bean
    public Queue orderDelayQueue() {
        /*
            Queue(String name,  队列名字
            boolean durable,  是否持久化
            boolean exclusive,  是否排他
            boolean autoDelete, 是否自动删除
            Map<String, Object> arguments) 属性
         */
        HashMap<String, Object> arguments = new HashMap<>();
        arguments.put("x-dead-letter-exchange", "order-event-exchange");
        arguments.put("x-dead-letter-routing-key", "order.release.order");
        arguments.put("x-message-ttl", 60000); // 消息过期时间 1分钟
        Queue queue = new Queue("order.delay.queue", true, false, false, arguments);

        return queue;
    }

    /**
     * 普通队列
     * @return
     */
    @Bean
    public Queue orderReleaseQueue() {
        Queue queue = new Queue("order.release.order.queue", true, false, false);
        return queue;
    }

    /**
     * TopicExchange
     * @return
     */
    @Bean
    public Exchange orderEventExchange() {
        /*
         *   String name,
         *   boolean durable,
         *   boolean autoDelete,
         *   Map<String, Object> arguments
         * */
        return new TopicExchange("order-event-exchange", true, false);

    }

    @Bean
    public Binding orderCreateOrderBinding() {
        /*
         * String destination, 目的地(队列名或者交换机名字)
         * DestinationType destinationType, 目的地类型(Queue、Exhcange)
         * String exchange,
         * String routingKey,
         * Map<String, Object> arguments
         * */
        return new Binding("order.delay.queue",
                Binding.DestinationType.QUEUE,
                "order-event-exchange",
                "order.create.order",
                null);
    }

    @Bean
    public Binding orderReleaseOrderBinding() {
        return new Binding("order.release.order.queue",
                Binding.DestinationType.QUEUE,
                "order-event-exchange",
                "order.release.order",
                null);
    }

}

商城图: image-20231004224805983

本文由mdnice多平台发布