RabbitMQ详解

RabbitMQ

1. RabbitMQ简介
  • 消息队列的基本概念
    消息队列是一种允许应用程序之间异步通信的中间件。它允许一个应用程序(生产者)将消息发送到队列中,而另一个应用程序(消费者)则可以从队列中读取消息。 它具有生产者(消息发送者) 和消费者 (消息接收者)
  • 消息队列如何选型
特性 ActiveMQ RabbitMQ RocketMQ Kafka
单机吞吐量 万级 万级 10 万级 10 万级
时效性 毫秒级 微秒级 毫秒级 毫秒级
可用性 高(主从) 高(主从) 非常高(分布式) 非常高(分布式)
消息重复 至少一次 至少一次 至少一次 最多一次 至少一次 最多一次
消息顺序性 有序 有序 有序 分区有序
支持主题数 千级 百万级 千级 百级,多了性能严重下滑
消息回溯 不支持 不支持 支持(按时间回溯) 支持(按offset回溯)
管理界面 普通 普通 完善 普通
  • 消息队列应用场景:
  1. 电子商务订单处理

    在电子商务平台中,订单生成后需要经过多个步骤,如库存检查、支付处理、物流安排等。消息队列可以将订单信息异步传递给各个服务,确保订单处理流程的高效和可靠。

  2. 日志聚合

    分布式系统中,各个服务节点会产生大量日志。消息队列可以收集这些日志信息,并将其发送到日志聚合服务进行集中处理和存储,便于后续的分析和监控。

  3. 用户行为追踪

    在网站或应用中,用户的行为(如点击、浏览、购买等)可以被记录并通过消息队列发送到数据处理服务,用于实时分析用户行为,优化用户体验。

  4. 系统监控和报警

    监控系统可以利用消息队列来收集各种监控指标,如CPU使用率、内存使用量等。当检测到异常情况时,可以通过消息队列触发报警机制,通知运维人员。

  5. 社交网络消息推送 在社交网络平台中,用户生成的内容(如帖子、评论、点赞等)可以通过消息队列推送给相关用户,确保消息的及时性和可靠性。

  6. 支付网关

    支付网关需要处理大量的支付请求,并与银行或支付服务提供商进行交互。消息队列可以用于排队支付请求,确保支付流程的稳定性和安全性。

  7. 文件处理服务

    在需要处理大量文件上传的场景中,可以将文件上传请求发送到消息队列,然后由专门的文件处理服务异步处理这些文件,如图片压缩、视频转码等,提高系统的响应速度和处理能力。

  • AMQP协议简介
    AMQP(高级消息队列协议)是一个为消息中间件设计的开放标准协议,它支持跨语言和跨平台的消息传递。AMQP采用二进制格式以提高传输效率,提供消息确认和持久化等可靠性保证,以及灵活的路由机制和多种消息模式,如点对点和发布/订阅。它还支持SASL认证和TLS/SSL加密,确保了消息传输的安全性。AMQP的多语言支持和互操作性使其成为企业级应用集成、微服务通信和大规模分布式系统中通信的坚实基础,广泛应用于需要高可靠性和灵活性的场景。
2. RabbitMQ核心组件
Exchange(交换机)类型:Direct、Fanout、Topic、Headers

在AMQP协议中,Exchange(交换机)是消息路由的核心组件,它根据特定的规则将消息路由到一个或多个队列。以下是四种主要的交换机类型:

  • Direct Exchange(直连交换机):

直连交换机根据消息的routing key(路由键)将消息路由到匹配的队列。

生产者发送消息时指定一个routing key,消费者在绑定队列到交换机时也指定一个routing key。

如果消息的routing key与队列的routing key完全匹配,消息就会被路由到该队列。

这是最常用的交换机类型,适用于简单的点对点通信场景。

  • Fanout Exchange(扇出交换机):

扇出交换机将消息路由到所有绑定的队列,而不考虑routing key。

它就像一个广播,将消息复制并发送到所有绑定的队列。

适用于发布/订阅模式,其中多个消费者都需要接收相同的消息副本。

  • Topic Exchange(主题交换机):

主题交换机根据routing key的模式匹配将消息路由到队列。

routing key通常是一个由点(.)分隔的字符串,如"sports.basketball"。

队列可以绑定到一个或多个模式,如"sports.#"(匹配所有以"sports."开头的routing key)或"sports.basketball.#"(匹配所有以"sports.basketball."开头的routing key)。

适用于复杂的路由场景,如多级分类或通配符匹配。

  • Headers Exchange(头交换机):

• 头交换机根据消息的header(头部)属性进行路由,而不是routing key。

• 生产者可以在发送消息时添加自定义的header属性,如{"priority":"high"}。

• 队列可以绑定到交换机时指定匹配的header条件,如{x-match:all,priority:"high"}。

• 适用于需要根据消息内容或元数据进行路由的场景。

Queue(队列)与Binding(绑定)

在消息队列系统中,Queue(队列)和Binding(绑定)是两个核心概念,它们共同工作以实现消息的路由和分发。

Queue(队列)

队列是消息的容器,用于存储等待被处理的消息。队列可以有以下特性:

  • 持久化:队列可以被设置为持久化,这意味着即使消息队列服务重启,队列中的消息也不会丢失。

  • 排他性:队列可以是排他的,意味着它只能被一个消费者使用,或者可以被多个消费者共享。

  • 自动删除:当队列不再被使用时,可以设置为自动删除。

  • 长度限制:队列可以设置最大长度,超过这个长度后,新的消息可能会被拒绝或旧的消息可能会被丢弃。

Binding(绑定)

绑定是队列和交换机之间的关联关系,它定义了消息如何从交换机路由到队列。绑定包含以下关键元素:

  • Routing Key(路由键):在某些类型的交换机(如Direct和Topic)中,路由键用于确定消息应该被路由到哪个队列。路由键是交换机和队列之间绑定的一部分。

  • Exchange(交换机):绑定定义了队列与哪个交换机相关联。

  • Arguments(参数):绑定可以包含额外的参数,这些参数可以用于配置绑定的行为,例如,设置绑定的优先级。

Message(消息)的结构与属性

在消息队列系统中,Message(消息)是传递信息的基本单元。消息的结构和属性可能因不同的消息队列系统(如RabbitMQ、Apache Kafka等)而略有不同,但通常包含以下几个核心部分:

  1. 消息体(Payload/Body)
  • 描述:消息体是消息的主要内容,包含了需要传输的数据。

  • 属性:消息体可以是文本(如JSON、XML、字符串等)、二进制数据(如文件、图像等)或其他任何可以序列化的数据格式。

  1. 消息头(Headers)
  • 描述:消息头包含了一些元数据,用于描述消息的属性或用于路由决策。

  • 属性:可以包括消息的优先级、时间戳、特定于应用的元数据(如用户ID、会话ID等)。

  1. 属性(Properties)
  • 描述:属性通常用于指定消息的传输属性,如持久化、延迟等。

  • 属性:

  • Delivery Mode:指定消息是否应该被持久化存储。通常有两个值:1(非持久化)和2(持久化)。

  • Priority:指定消息的优先级,用于在队列中确定消息的处理顺序。

  • Correlation ID:用于将消息与特定的事件或操作关联起来,方便追踪。

  • Reply To:指定消息的回复地址,用于请求/响应模式。

  • Expiration:指定消息的生存时间,超过这个时间后消息将被自动删除。

  1. 路由键(Routing Key)
  • 描述:在某些消息队列系统中,路由键用于指定消息的路由路径,特别是在使用Direct、Topic或Fanout类型的交换机时。

  • 属性:路由键是字符串类型的,其值取决于消息的发送者和接收者之间的约定。

  1. 交换机(Exchange)
  • 描述:交换机是消息队列中的一个组件,负责接收消息并将它们路由到一个或多个队列。

  • 属性:交换机的类型(Direct、Topic、Fanout、Headers)和名称。

  1. 队列(Queue)
  • 描述:队列是存储消息的目的地,消费者从队列中获取消息进行处理。

  • 属性:队列的名称和可能的其他队列特定的属性(如持久化、排他性等)。

3. Java集成RabbitMQ
  • 引入依赖(如spring-boot-starter-amqpamqp-client
java 复制代码
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-amqp</artifactId>
        <version>3.1.5</version>
    </dependency>
</dependencies>
  • 连接工厂(ConnectionFactory)配置
    application.yml
xml 复制代码
spring:
  rabbitmq:
    host: localhost
    port: 5672
    username: guest
    password: guest
java 复制代码
import com.rabbitmq.client.ConnectionFactory;

public class RabbitMQConfig {
    public static ConnectionFactory createConnectionFactory() {
        ConnectionFactory factory = new ConnectionFactory();
        
        // 设置RabbitMQ服务器地址
        factory.setHost("localhost");
        
        // 设置RabbitMQ服务器端口,默认是5672
        factory.setPort(5672);
        
        // 设置虚拟主机,默认是"/"
        factory.setVirtualHost("/");
        
        // 设置用户名,默认是"guest"
        factory.setUsername("guest");
        
        // 设置密码,默认是"guest"
        factory.setPassword("guest");
        
        // 设置连接超时时间(毫秒)
        factory.setConnectionTimeout(30000);
        
        // 设置请求的心跳间隔(秒)
        factory.setRequestedHeartbeat(60);
        
        // 设置自动恢复连接
        factory.setAutomaticRecoveryEnabled(true);
        
        return factory;
    }
}
  • 创建连接、通道(Channel)与队列声明
java 复制代码
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.core.TopicExchange;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class RabbitMQConfig {

    @Bean
    public Queue myQueue() {
        return new Queue("myQueue", true);
    }

    @Bean
    public TopicExchange myExchange() {
        return new TopicExchange("myExchange");
    }

    @Bean
    public Binding binding(Queue myQueue, TopicExchange myExchange) {
        return BindingBuilder.bind(myQueue).to(myExchange).with("my.routing.key");
    }
}
4. 消息生产与消费
  • 生产者发送消息(basicPublish

直接向队列myQueue发送消息

java 复制代码
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class MessageProducer {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    public void sendMessage() {
        rabbitTemplate.convertAndSend("myQueue","hello  mq!");
    }
}

向交换机发送消息

java 复制代码
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class MessageProducer {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    public void sendMessage() {
        rabbitTemplate.convertAndSend("myExchange", "myRoutingKey", "hello mq!");
    }
}
  • 消费者监听队列(basicConsume
java 复制代码
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

@Component
public class MessageListener {

    @RabbitListener(queues = "myQueue")
    public void receiveMessage(String message) {
        System.out.println("Received message: " + message);
    }
}
java 复制代码
@Component
public class MessageListener {

    @RabbitListener(bindings = @QueueBinding(
        value = @Queue(name = "myQueue", durable = "true"),
        exchange = @Exchange(name = "myExchange", type = ExchangeTypes.TOPIC),
        key = "my.routing.key"
    ))
    public void receiveMessage(String message) {
        System.out.println("Received message: " + message);
    }
}
  • 消息确认机制(ACK/NACK)
    在RabbitMQ中,消息确认机制(也称为ACK/NACK)是一种确保消息可靠传递的重要机制。它允许消息消费者在处理完消息后向消息队列发送确认信号,告知消息已被成功处理。这样可以防止消息丢失,并确保消息至少被消费一次。

消息确认(ACK)

当消费者成功处理一条消息后,它会发送一个ACK(确认)信号回RabbitMQ。RabbitMQ收到ACK后,会从队列中删除该消息。ACK机制确保了消息不会被重复处理,除非消费者处理消息失败。

消息拒绝(NACK)

如果消费者无法处理一条消息(例如,由于消息格式错误或内部错误),它可以发送一个NACK(拒绝)信号回RabbitMQ。RabbitMQ收到NACK后,可以选择将消息重新入队,以便其他消费者可以重新处理,或者将其丢弃,具体取决于NACK的配置。

自动确认与手动确认

RabbitMQ支持两种消息确认模式:

  • 自动确认(Auto-ACK):

在这种模式下,消费者从队列中接收消息后,RabbitMQ会自动发送ACK确认,消息会被立即删除。

这种模式简单,但不够灵活,因为它不提供错误处理机制。

  • 手动确认(Manual-ACK):

在这种模式下,消费者需要显式地发送ACK或NACK信号。

这种模式更灵活,允许消费者在处理消息后决定是否确认消息,从而实现更复杂的错误处理和消息重试逻辑。

使用Spring AMQP实现手动确认

在Spring AMQP中,你可以通过配置SimpleRabbitListenerContainerFactory来启用手动确认模式:

java 复制代码
import org.springframework.amqp.rabbit.config.SimpleRabbitListenerContainerFactory;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.listener.RabbitListenerEndpointRegistrar;
import org.springframework.amqp.rabbit.listener.api.ChannelAwareMessageListener;
import org.springframework.amqp.rabbit.listener.api.MessageListener;
import org.springframework.amqp.rabbit.listener.api.SimpleMessageListenerContainer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class RabbitMQConfig {

    @Bean
    public SimpleRabbitListenerContainerFactory myFactory(ConnectionFactory connectionFactory) {
        SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
        factory.setConnectionFactory(connectionFactory);
        factory.setAcknowledgeMode(AcknowledgeMode.MANUAL);
        factory.setConcurrentConsumers(3);
        factory.setMaxConcurrentConsumers(10);
        return factory;
    }
}

然后,在消费者方法中手动处理ACK:

java 复制代码
@Component
public class MessageListener {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @RabbitListener(queues = "myQueue", containerFactory = "myFactory")
    public void receiveMessage(String message, Channel channel, @Header("deliveryTag") long deliveryTag) throws IOException {
        try {
            // 处理消息
            System.out.println("Received message: " + message);

            // 手动发送ACK
            channel.basicAck(deliveryTag, false);
        } catch (Exception e) {
            // 处理消息失败,发送NACK
            channel.basicNack(deliveryTag, false, true);
        }
    }
}

在这个例子中,我们使用Channel对象来手动发送ACK或NACK。basicAck方法用于确认消息,而basicNack方法用于拒绝消息。

通过使用手动确认机制,你可以更灵活地处理消息,并确保消息的可靠传递。

5. 高级特性与实践
  • 消息持久化(deliveryMode=2
    将消息和队列信息写入磁盘,确保在 RabbitMQ Broker 重启后消息不丢失。

默认情况下,消息仅存储在内存中,Broker 崩溃即丢失

开启持久化后,消息会被写入磁盘日志文件

是实现 At-Least-Once Delivery(至少一次投递) 的基础

  • 死信队列(DLX)与TTL设置

定义交换机 队列和绑定

java 复制代码
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.DurableQueue;
import org.springframework.amqp.core.Exchange;
import org.springframework.amqp.core.ExchangeBuilder;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.core.TopicExchange;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class RabbitMQConfig {

    @Bean
    public Queue mainQueue() {
        return new DurableQueue("mainQueue", true);
    }

    @Bean
    public Queue deadLetterQueue() {
        return new DurableQueue("deadLetterQueue", true);
    }

    @Bean
    public Exchange mainExchange() {
        return ExchangeBuilder.topicExchange("mainExchange").durable(true).build();
    }

    @Bean
    public Exchange deadLetterExchange() {
        return ExchangeBuilder.topicExchange("deadLetterExchange").durable(true).build();
    }

    @Bean
    public Binding mainBinding(Queue mainQueue, Exchange mainExchange) {
        return BindingBuilder.bind(mainQueue).to(mainExchange).with("main_routing_key");
    }

    @Bean
    public Binding deadLetterBinding(Queue deadLetterQueue, Exchange deadLetterExchange, Queue mainQueue) {
        return BindingBuilder.bind(deadLetterQueue).to(deadLetterExchange).with("deadLetter_routing_key");
    }
}

配置消费者

java 复制代码
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

@Component
public class MessageListener {

    @RabbitListener(queues = "mainQueue")
    public void receiveMessage(String message) {
        System.out.println("Received message: " + message);
        // 模拟处理失败
        throw new RuntimeException("Failed to process message");
    }
}

在application.yml 中配置死信队列(DLX)与TTL设置

xml 复制代码
spring:
  rabbitmq:
    listener:
      simple:
        prefetch: 1
        acknowledge-mode: auto
        concurrency: 3
        max-concurrency: 10
    queues:
      - name: mainQueue
        durable: true
        exclusive: false
        auto-delete: false
        dead-letter-exchange: deadLetterExchange
        ttl: 60000  # 设置TTL为60秒
    exchanges:
      - name: mainExchange
        type: topic
        durable: true
    bindings:
      - queue: mainQueue
        exchange: mainExchange
        routing-key: main_routing_key
      - queue: deadLetterQueue
        exchange: deadLetterExchange
        routing-key: deadLetter_routing_key
6. Spring Boot集成示例
  • @RabbitListener注解实现监听
java 复制代码
@Component
public class MessageListener {

    @RabbitListener(bindings = @QueueBinding(
        value = @Queue(name = "myQueue", durable = "true"),
        exchange = @Exchange(name = "myExchange", type = ExchangeTypes.TOPIC),
        key = "my.routing.key"
    ))
    public void receiveMessage(String message) {
        System.out.println("Received message: " + message);
    }
}
  • RabbitTemplate发送消息
java 复制代码
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class MessageProducer {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    public void sendMessage() {
        rabbitTemplate.convertAndSend("myExchange", "myRoutingKey", "hello mq!");
    }
}
  • 配置类(@Configuration)定义Exchange/Queue
java 复制代码
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.core.TopicExchange;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class RabbitMQConfig {

    @Bean
    public Queue myQueue() {
        return new Queue("myQueue", true);
    }

    @Bean
    public TopicExchange myExchange() {
        return new TopicExchange("myExchange");
    }

    @Bean
    public Binding binding(Queue myQueue, TopicExchange myExchange) {
        return BindingBuilder.bind(myQueue).to(myExchange).with("my.routing.key");
    }
}
相关推荐
Tom·Ge7 分钟前
Spring AI 入门指南:三步将AI集成到Spring Boot应用
人工智能·spring boot·spring
华仔啊11 分钟前
别再问了!Java里这几种场景,用抽象类就对了
java·后端
明天过后012228 分钟前
PDF文件中的相邻页面合并成一页,例如将第1页和第2页合并,第3页和第4页合并
java·python·pdf
tingting011929 分钟前
Spring Boot 外部配置指定不生效的原因与解决
java·spring boot·后端
亲爱的非洲野猪34 分钟前
Spring Boot 与传统 Spring:从 WAR 到可执行 JAR,颠覆性的部署哲学
spring boot·spring·jar
用户03321266636740 分钟前
Java 设置 Excel 行高列宽:告别手动调整,拥抱自动化高效!
java·excel
2501_9096867040 分钟前
基于SpringBoot的网上点餐系统
java·spring boot·后端
neoooo1 小时前
Spring Boot 3 + Kafka 实战指南
java·spring boot·kafka
天天摸鱼的java工程师1 小时前
聊聊线程池中哪几种状态,分别表示什么?8 年 Java 开发:从业务踩坑到源码拆解(附监控实战)
java·后端
杨杨杨大侠1 小时前
第4篇:AOP切面编程 - 无侵入式日志拦截
java·后端·开源