RabbitMq

什么是RabbitMQ,有什么特点
  1. 消息传递模式:RabbitMQ支持多种消息传递模式,包括发布/订阅、点对点和工作队列等,使其更灵活适用于各种消息通信场景。
  2. 消息路由和交换机:RabbitMQ引入交换机的概念,用于将消息路由到一个或多个队列。允许根据消息的内容、标签或路由键进行灵活的消息路由,从而实现更复杂的消息传递逻辑。
  3. 消息确认机制:RabbitMQ支持消息确认机制,消费者可以确定已成功处理消息。确保了消息不会再传递后被重复消费,增加了消息的可靠性。
  4. 消息持久性:RabbitMQ允许消息和队列的持久性设置,确保消息再RabbitMQ重新启动后不会丢失。这对于关键的业务消息非常重要。
RabbitMQ和AMQP是什么关系
  • AMQP:AMQP是一个协议规范,而不是一个具体的消息中间件。它是一个开放的消息产地协议,是一种应用层的标准协议,为面向消息的中间件设计。AMQP提供了一种同统一的消息服务,使得不同程序之间可以通过消息队列进行通信。SpringBoot框架默认就提供了对AMQP协议的支持。
  • RabbitMQ:RabbitMQ是一种开源的消息中间件,是一个具体的软件产品。使用AMQP协议来实现消息传递的标准。并且其也支持其他消息传递协议,如STOMP和MQTT。RabbitMQ基于AMQP协议定义的消息格式和交互流程,实现了消息再生产者、交换机队列之间的传递和处理。
RabbitMQ的核心组件有哪些
  1. Broker:RabbitMQ服务器,负责接收和分发消息的应用。
  2. Virtual Host: 虚拟主机,是RabbitMQ中的逻辑容器,用于隔离不同环境或不同应用程序的信息流。每个虚拟主机都有自己的队列交换机等设置,可以理解为一个独立的RabbitMQ服务。
  3. Connection连接:管理和维护与RabbitMQ服务器的TCP连接,生产者、消费者通过这个连接和Broker建立物理网络连接。
  4. Channel通道:是在Connection内创建的轻量级通信通道,用于进行消息的传输和交互。应用程序通过Channel进行消息的发送和接收。通常一个Connection可以建立多个Channel。
  5. Exchange交换机:交换机是消息的中转站,负责接收来自生产者的消息,并将其路由到一个或多个队列中。RabbitMQ提供多种不同类型的交换机,每种不同类型的交换机都有不同的消息路由规则。
  6. Queue队列:队列是消息的存储位置。每个队列都有一个唯一的名称。消息从交换机路由到队列,然后等待消费者消费。
  7. Binding绑定关系:Binding是Exchange和Queue之间的关联规则,定义了消息如何从交换机路由到特定队列。
RabbitMQ的交换机的类型
  • Direct Exchange:直连交换机,这种交换机根据消息的路由键(Routing Key)将消息发送到与之完全匹配的队列。只有当消息的路由键与队列绑定的路由键相同时,消息才会被路由到队列,是一种简单的路由策略,适用于点对点通信。

    //配置类
    import org.springframework.amqp.core.*;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    
    @Configuration
    public class DirectExchangeConfig {
    
        public static final String DIRECT_EXCHANGE_NAME = "direct-exchange";
        public static final String QUEUE_A_NAME = "queue-a";
        public static final String QUEUE_B_NAME = "queue-b";
        public static final String ROUTING_KEY_A = "key-a";
        public static final String ROUTING_KEY_B = "key-b";
    
        @Bean
        DirectExchange directExchange() {
            return new DirectExchange(DIRECT_EXCHANGE_NAME);
        }
    
        @Bean
        Queue queueA() {
            return new Queue(QUEUE_A_NAME);
        }
    
        @Bean
        Queue queueB() {
            return new Queue(QUEUE_B_NAME);
        }
    
        @Bean
        Binding bindQueueAToDirect(Queue queueA, DirectExchange directExchange) {
            return BindingBuilder.bind(queueA).to(directExchange).with(ROUTING_KEY_A);
        }
    
        @Bean
        Binding bindQueueBToDirect(Queue queueB, DirectExchange directExchange) {
            return BindingBuilder.bind(queueB).to(directExchange).with(ROUTING_KEY_B);
        }
    }
    
    //生产者
    import org.springframework.amqp.core.AmqpTemplate;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Component;
    
    @Component
    public class DirectProducer {
    
        private final AmqpTemplate rabbitTemplate;
    
        @Autowired
        public DirectProducer(AmqpTemplate rabbitTemplate) {
            this.rabbitTemplate = rabbitTemplate;
        }
    
        public void send(String routingKey, String message) {
            rabbitTemplate.convertAndSend(DirectExchangeConfig.DIRECT_EXCHANGE_NAME, routingKey, message);
        }
    }
    //消费者
    import org.springframework.amqp.rabbit.annotation.RabbitListener;
    import org.springframework.stereotype.Component;
    
    @Component
    public class DirectConsumerA {
    
        @RabbitListener(queues = DirectExchangeConfig.QUEUE_A_NAME)
        public void receiveA(String in) {
            System.out.println("Queue A received: '" + in + "'");
        }
    }
    
    @Component
    public class DirectConsumerB {
    
        @RabbitListener(queues = DirectExchangeConfig.QUEUE_B_NAME)
        public void receiveB(String in) {
            System.out.println("Queue B received: '" + in + "'");
        }
    }
    
  • Topic Exchange:主题交换机,这种交换机通过通配符匹配,根据消息的路由键与队列绑定时指定的路由模式匹配程度(#表示一个或多个词,*表示一个词。),将消息路由到一个或者是多个队列。多用于发布/订阅模式和复杂的消息路由需求。

    //配置类
    @Configuration
    public class TopicExchangeConfig {
    
        public static final String TOPIC_EXCHANGE_NAME = "topic-exchange";
        public static final String QUEUE_A_NAME = "queue-a";
        public static final String QUEUE_B_NAME = "queue-b";
    
        @Bean
        TopicExchange topicExchange() {
            return new TopicExchange(TOPIC_EXCHANGE_NAME);
        }
    
        @Bean
        Queue queueA() {
            return new Queue(QUEUE_A_NAME);
        }
    
        @Bean
        Queue queueB() {
            return new Queue(QUEUE_B_NAME);
        }
    
        @Bean
        Binding bindQueueAToTopic(Queue queueA, TopicExchange topicExchange) {
            return BindingBuilder.bind(queueA).to(topicExchange).with("key.*");
        }
    
        @Bean
        Binding bindQueueBToTopic(Queue queueB, TopicExchange topicExchange) {
            return BindingBuilder.bind(queueB).to(topicExchange).with("*.key");
        }
    }//队列queue-a绑定到主题交换机上的key.*模式,这意味着任何以key.开头的routing key都会被路由到queue-a。队列queue-b绑定到*.key模式,这意味着任何以.key结尾的routing key都会被路由到queue-b
    
    //生产者
    import org.springframework.amqp.core.AmqpTemplate;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Component;
    
    @Component
    public class TopicProducer {
    
        private final AmqpTemplate rabbitTemplate;
    
        @Autowired
        public TopicProducer(AmqpTemplate rabbitTemplate) {
            this.rabbitTemplate = rabbitTemplate;
        }
    
        public void send(String routingKey, String message) {
            rabbitTemplate.convertAndSend(TopicExchangeConfig.TOPIC_EXCHANGE_NAME, routingKey, message);
        }
    }//当 routingKey="hello.key" 则会路由到queueB
    
    //消费者
    @Component
    public class TopicConsumerA {
    
        @RabbitListener(queues = TopicExchangeConfig.QUEUE_A_NAME)
        public void receiveA(String in) {
            System.out.println("Queue A received: '" + in + "'");
        }
    }
    
    @Component
    public class TopicConsumerB {
    
        @RabbitListener(queues = TopicExchangeConfig.QUEUE_B_NAME)
        public void receiveB(String in) {
            System.out.println("Queue B received: '" + in + "'");
        }
    }
    
  • Headers Exchange:头交换机,不处理路由键, 而是根据发送的消息内容中的headers属性进行匹配 。只有当消息的标头和绑定规则完全匹配时,消息才会被路由到队列。适用于需要复杂消息匹配的场景。

    //配置类
    @Bean
        HeadersExchange headersExchange() {
            return new HeadersExchange(HEADER_EXCHANGE_NAME);
        }
        @Bean
        Queue queue() {
            return new Queue(QUEUE_NAME);
        }
        @Bean
        Binding binding(Queue queue, HeadersExchange headersExchange) {
            return BindingBuilder.bind(queue).to(headersExchange).whereAll(new String[]{"x-match", "id"}).matches("true", "123");
        }//whereAll:必须匹配所有的键值对  whereAny:至少有一个匹配的键值对
    
    //生产者
    import org.springframework.amqp.core.Message;
    import org.springframework.amqp.core.MessageProperties;
    import org.springframework.amqp.rabbit.core.RabbitTemplate;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Component;
    
    @Component
    public class HeaderProducer {
    
        private final RabbitTemplate rabbitTemplate;
    
        @Autowired
        public HeaderProducer(RabbitTemplate rabbitTemplate) {
            this.rabbitTemplate = rabbitTemplate;
        }
    
        public void send(String message) {
            MessageProperties props = new MessageProperties();
            props.setHeader("x-match", "true");
            props.setHeader("id", "123");
            Message msg = new Message(message.getBytes(), props);
            rabbitTemplate.convertAndSend(HeaderExchangeConfig.HEADER_EXCHANGE_NAME, "", msg);
        }
    }
    //消费者
    import org.springframework.amqp.rabbit.annotation.RabbitListener;
    import org.springframework.stereotype.Component;
    
    @Component
    public class HeaderConsumer {
    
        @RabbitListener(queues = HeaderExchangeConfig.QUEUE_NAME)
        public void receive(String in) {
            System.out.println("Received: '" + in + "'");
        }
    }
    
  • Fanout Exchange:扇形交换机,采用广播的方式,根据绑定的交换机路由到与之对应的所有队列。用于发布/订阅模式,其中一个消息被广播给所有订阅者。

    //配置类   
    public static final String FANOUT_EXCHANGE_NAME = "fanout-exchange";
        public static final String QUEUE_A_NAME = "queue-a";
        public static final String QUEUE_B_NAME = "queue-b";
    
        @Bean
        FanoutExchange fanoutExchange() {
            return new FanoutExchange(FANOUT_EXCHANGE_NAME);
        }
    
        @Bean
        Queue queueA() {
            return new Queue(QUEUE_A_NAME);
        }
    
        @Bean
        Queue queueB() {
            return new Queue(QUEUE_B_NAME);
        }
    
        @Bean
        Binding bindQueueAToFanout(Queue queueA, FanoutExchange fanoutExchange) {
            return BindingBuilder.bind(queueA).to(fanoutExchange);
        }
    
        @Bean
        Binding bindQueueBToFanout(Queue queueB, FanoutExchange fanoutExchange) {
            return BindingBuilder.bind(queueB).to(fanoutExchange);
        }
    
    //生产者
    import org.springframework.amqp.core.AmqpTemplate;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Component;
    
    @Component
    public class FanoutProducer {
    
        private final AmqpTemplate rabbitTemplate;
    
        @Autowired
        public FanoutProducer(AmqpTemplate rabbitTemplate) {
            this.rabbitTemplate = rabbitTemplate;
        }
    
        public void send(String message) {
            rabbitTemplate.convertAndSend(FanoutExchangeConfig.FANOUT_EXCHANGE_NAME, "", message);
        }
    }
    //消费者
    import org.springframework.amqp.rabbit.annotation.RabbitListener;
    import org.springframework.stereotype.Component;
    
    @Component
    public class FanoutConsumerA {
    
        @RabbitListener(queues = FanoutExchangeConfig.QUEUE_A_NAME)
        public void receiveA(String in) {
            System.out.println("Queue A received: '" + in + "'");
        }
    }
    
    @Component
    public class FanoutConsumerB {
    
        @RabbitListener(queues = FanoutExchangeConfig.QUEUE_B_NAME)
        public void receiveB(String in) {
            System.out.println("Queue B received: '" + in + "'");
        }
    }
    
    RabbitMq如何保证消息不丢失?
    • 消息丢失得原因分析如下图:

      请添加图片描述
      从图可知 生产者的消息到达消费者会经过两次网络传输,并且会在RabbitMq服务器中进行路由。

所以一般有以下三种丢失的场景:

  1. 生产者消息发送到RabbitMq服务器过程中出现消息丢失。可能由于网络波动,或者是服务器宕机。
  2. RabbitMQ 服务器消息持久化出现消息丢失。 消息发送到 RabbitMQ 之后,未能及时存储完成持久化,RabbitMQ 服务器出现宕机重启,消息出现丢失。
  3. 消费者拉取消息过程以及拿到消息后出现消息丢失。消费者从 RabbitMQ 服务器获取到消息过程出现网络波动等问题可能出现消息丢失;消费者拿到消息后但是消费者未能正常消费,导致丢失,可能是消费者出现处理异常又或者是消费者宕机。

针对上述三种消息丢失场景,RabbitMQ 提供了相应的解决方案,confirm 消息确认机制(生产者),消息持久化机制(RabbitMQ 服务),ACK事务机制(消费者)

  • confirm消息确认机制(生产者)

Confirm 模式是 RabbitMQ 提供的一种消息可靠性保障机制。当生产者通过 Confirm 模式发送消息时,它会等待 RabbitMQ 的确认,确保消息已经被正确地投递到了指定的 Exchange 中。消息正确投递到 queue 时,会返回 ack。
消息没有正确投递到 queue 时,会返回 nack。如果 exchange 没有绑定 queue,也会出现消息丢失。示例代码如下:

配置类代码如下:

@Configuration
public class RabbitConfig {

    @Bean
    public AmqpTemplate rabbitTemplate(ConnectionFactory connectionFactory) {
        RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
        rabbitTemplate.setMessageConverter(producerJackson2MessageConverter());
        rabbitTemplate.setConfirmCallback((correlationData, ack, cause) -> {
            if (ack) {
                System.out.println("Message with id: " + correlationData.getId() + " successfully sent");
            } else {
                System.out.println("Message with id: " + correlationData.getId() + " failed to send due to: " + cause);
            }
        });
        rabbitTemplate.setReturnCallback((message, replyCode, replyText, exchange, routingKey) -> {
            System.out.println("Returned message: " + new String(message.getBody()));
            System.out.println("Exchange: " + exchange);
            System.out.println("Routing key: " + routingKey);
            System.out.println("Reply code: " + replyCode);
            System.out.println("Reply text: " + replyText);
        });
        return rabbitTemplate;
    }

    @Bean
    public MessageConverter producerJackson2MessageConverter() {
        return new Jackson2JsonMessageConverter();
    }
}

生产者代码如下:

import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class MessageProducerService {

    private final RabbitTemplate rabbitTemplate;

    @Autowired
    public MessageProducerService(RabbitTemplate rabbitTemplate) {
        this.rabbitTemplate = rabbitTemplate;
    }

    @Transactional
    public void sendMessage(String message) {
        try {
            // 发送消息
            rabbitTemplate.convertAndSend("exchange-name", "routing-key", message);
            // 业务逻辑
            doBusinessLogic();
        } catch (Exception e) {
            // 处理异常
            throw new RuntimeException(e);
        }
    }

    private void doBusinessLogic() {
        // 业务逻辑代码
    }
}

RabbitTemplate在Spring AMQP中提供了多种机制来确认消息是否成功传递给RabbitMQ以及消息是否成功路由到预期的队列。setConfirmCallbacksetReturnCallback是其中的两种重要机制,它们分别在不同的情况下被触发,用于处理消息的确认和返回。

  1. setConfirmCallback

setConfirmCallback方法用于设置一个回调,当消息被投递到RabbitMQ的Broker后,Broker会发送一个确认信号回给生产者,表明消息已经被接收。这个确认信号包含了消息的唯一标识符,称为CorrelationData,通常是一个UUID或其他可识别的标识符,以及一个布尔值ack,表明消息是否被成功接收。

触发时机

  • 消息到达Broker:当消息到达Broker并被放置在队列中或在尝试放置消息时失败,Broker会发送确认信号。
  • 消息持久化:如果消息被设置为持久化的,Broker在将消息写入磁盘后才发送确认信号。

使用场景

  • 确认消息投递:在高可用性或关键业务流程中,需要确认每条消息都被Broker接收。
  • 性能优化:在高吞吐量的应用中,可以使用发布确认的批量确认模式以减少网络开销。
  1. setReturnCallback

setReturnCallback方法用于设置一个回调,当消息无法被正确路由到任何一个队列时,Broker会将消息返回给生产者。这通常发生在以下情况:

  • Routing Key不存在:如果使用Direct或Fanout交换机时,没有队列与指定的Routing Key绑定。
  • Routing Key不匹配:如果使用Topic或Header交换机时,消息的Routing Key与队列绑定的模式不匹配。
  • Mandatory标志位:当发送消息时设置了mandatory标志位为true,并且消息不能被正确路由到队列时,Broker会返回消息。
  • Immediate标志位:当发送消息时设置了immediate标志位为true,并且消息不能立即被消费时,Broker会返回消息。

触发时机

  • 消息未被路由:当消息因为上述原因未能被正确路由到队列时,Broker会返回消息。

使用场景

  • 错误检测:在开发阶段或生产环境中,可以帮助检测和调试Routing Key的错误或配置问题。
  • 消息重试或记录:对于未能路由的消息,可以设计逻辑来重新发送消息或记录日志以供后续分析。

综合使用

通常,setConfirmCallbacksetReturnCallback会被一起使用,以实现更全面的消息确认和错误处理策略。这确保了消息不仅被Broker接收,而且也被正确地路由到预期的队列中。如果消息未能正确路由,可以通过setReturnCallback回调进行处理,如重新发送消息、记录错误或采取其他补救措施。

注意事项

  • 当使用setConfirmCallback时,需要考虑网络延迟和Broker的确认延迟,这可能影响确认信号的及时性。
  • 当使用setReturnCallback时,需要处理好返回的消息,避免无限循环发送或不当的处理导致的问题。

相关推荐

  1. RabbitMQ

    2024-07-19 07:02:03       60 阅读
  2. <span style='color:red;'>RabbitMQ</span>

    RabbitMQ

    2024-07-19 07:02:03      57 阅读
  3. <span style='color:red;'>RabbitMQ</span>

    RabbitMQ

    2024-07-19 07:02:03      56 阅读
  4. RabbitMQ

    2024-07-19 07:02:03       57 阅读
  5. <span style='color:red;'>RabbitMQ</span>

    RabbitMQ

    2024-07-19 07:02:03      49 阅读
  6. RabbitMq

    2024-07-19 07:02:03       61 阅读
  7. <span style='color:red;'>RabbitMq</span>

    RabbitMq

    2024-07-19 07:02:03      45 阅读
  8. <span style='color:red;'>RabbitMQ</span>

    RabbitMQ

    2024-07-19 07:02:03      44 阅读
  9. <span style='color:red;'>RabbitMQ</span>

    RabbitMQ

    2024-07-19 07:02:03      53 阅读
  10. <span style='color:red;'>RabbitMQ</span>

    RabbitMQ

    2024-07-19 07:02:03      40 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-07-19 07:02:03       67 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-07-19 07:02:03       72 阅读
  3. 在Django里面运行非项目文件

    2024-07-19 07:02:03       58 阅读
  4. Python语言-面向对象

    2024-07-19 07:02:03       69 阅读

热门阅读

  1. vite + vue3 + uniapp 项目从零搭建

    2024-07-19 07:02:03       22 阅读
  2. React中Hooks几个有用的 ref

    2024-07-19 07:02:03       19 阅读
  3. Android RecyclerView实现Item单条滑动,一屏滑动

    2024-07-19 07:02:03       17 阅读
  4. crc、ecc、 uboot和 boot的原理分析及详解

    2024-07-19 07:02:03       22 阅读
  5. React Hook 总结(React 萌新升级打怪中...)

    2024-07-19 07:02:03       21 阅读
  6. linux路由基础知识(一)

    2024-07-19 07:02:03       16 阅读
  7. Activiti7+ SpringBoot+SpringMVC 开发

    2024-07-19 07:02:03       19 阅读
  8. AWS backup服务和 RDS snapshot的关系

    2024-07-19 07:02:03       22 阅读