消息队列选型终极指南:Kafka、RocketMQ、RabbitMQ 底层原理与场景化选型全解

0 阅读23分钟

一、消息队列核心底层原理

消息队列是分布式系统的核心基础设施,其本质是以异步通信为核心,实现系统解耦、流量削峰与数据分发的中间件。想要做好选型,必须先吃透其通用底层逻辑,避免陷入“只看API、不懂原理”的选型误区。

1.1 核心价值与通信模型

1.1.1 三大核心价值

  • 异步解耦:将同步强依赖调用转为异步消息通信,上下游系统只需关注消息协议,无需感知对方的可用性与实现逻辑,大幅降低系统耦合度。
  • 流量削峰:将前端突发流量缓存至消息队列,下游系统按自身处理能力匀速消费,避免流量洪峰导致的系统雪崩,是秒杀、大促等场景的核心解决方案。
  • 数据分发:基于发布订阅模型,实现一条消息向多个下游系统的广播分发,避免重复开发数据推送逻辑,提升系统扩展性。

1.1.2 两大核心通信模型

  • 点对点模型:消息发送至队列,仅能被一个消费者消费,适用于订单处理、短信发送等排他性消费场景。
  • 发布订阅模型:消息发送至主题,所有订阅该主题的消费者均可消费同一条消息,适用于日志广播、状态同步等多消费方场景。

1.2 消息投递语义与核心模块

1.2.1 三大投递语义

消息队列的所有可靠性设计,都围绕这三种语义展开,是选型的核心参考维度:

  • At-most-once(最多一次) :消息仅投递一次,可能丢失但绝对不会重复,适用于日志采集等允许少量数据丢失的场景。
  • At-least-once(至少一次) :消息保证不丢失,但可能重复投递,绝大多数业务场景的默认选择,需消费端实现幂等处理。
  • Exactly-once(精确一次) :消息严格保证不丢不重,仅投递一次,是金融、支付等强一致性场景的核心需求,实现成本最高。

1.2.2 核心底层模块

  • 生产端:负责消息序列化、分区路由、批量发送、失败重试,是消息投递的入口,核心设计围绕吞吐量与投递可靠性展开。
  • Broker服务端:消息队列的核心,包含网络通信层、存储引擎、副本同步、消费调度、重试/死信管理模块,决定了消息队列的性能、可靠性与功能上限。
  • 消费端:负责消息拉取/接收、反序列化、业务处理、offset提交、失败重试,核心设计围绕消费能力、负载均衡与消息可靠性展开。

1.3 高可用与持久化核心机制

1.3.1 持久化核心逻辑

所有消息队列的持久化设计,都遵循顺序写磁盘的核心原则,通过规避随机IO的性能损耗,实现磁盘持久化与高性能的平衡。核心分为两种刷盘策略:

  • 同步刷盘:消息写入磁盘后才返回生产端成功,可靠性最高,性能损耗最大。
  • 异步刷盘:消息写入操作系统页缓存后即返回成功,由操作系统异步刷盘,性能最高,存在机器宕机时的数据丢失风险。

1.3.2 高可用核心机制

分布式场景下,单节点故障不可避免,三大消息队列均通过副本机制实现高可用,核心逻辑是将同一份数据存储在多个节点,主节点故障时,从节点可切换为主节点继续提供服务,核心差异在于副本同步协议与故障转移机制。

二、三大主流消息队列深度拆解

2.1 RabbitMQ:轻量级高可靠的企业级消息中间件

RabbitMQ是基于AMQP协议、用Erlang语言开发的消息中间件,凭借轻量级、高可靠、路由灵活的特性,成为企业级应用的主流选择。

2.1.1 核心架构与底层原理

RabbitMQ的核心架构围绕AMQP协议展开,核心组件如下:

  • Virtual Host:虚拟主机,实现多租户隔离,不同虚拟主机的Exchange、Queue完全隔离。

  • Exchange:交换机,接收生产端发送的消息,根据路由规则将消息分发至对应的队列,核心支持4种类型:

    • Direct:精准匹配路由键,适用于点对点消息投递。
    • Topic:通配符匹配路由键,支持*匹配一个单词、#匹配多个单词,适用于多条件路由场景。
    • Fanout:广播模式,将消息分发至所有绑定的队列,无视路由键,适用于发布订阅场景。
    • Headers:根据消息头属性匹配路由,使用场景极少。
  • Binding:绑定关系,定义Exchange与Queue之间的路由规则,是RabbitMQ灵活路由能力的核心。

  • Queue:队列,存储消息的实体,消费者从队列中获取消息,支持消息持久化、排他、自动删除等属性。

2.1.2 核心特性与优劣势

  • 存储机制:默认将消息存储至Erlang内置的Mnesia数据库,支持持久化消息与非持久化消息,持久化消息会同步写入磁盘,非持久化消息仅存储在内存中。
  • 高可用机制:支持两种集群模式,普通集群模式仅共享元数据,消息仅存储在单个节点,无高可用能力;镜像队列模式会将队列的消息同步至多个节点,主节点故障时自动切换从节点,实现高可用。
  • 消费模式:默认采用推模式,Broker主动将消息推送至消费者,可通过prefetch参数限制消费者的预取数量,避免消费者被消息压垮。
  • 核心优势:轻量级部署成本极低,路由规则极其灵活,支持延迟队列、消息优先级、死信队列等丰富的企业级特性,生态完善,文档齐全,学习成本低。
  • 核心劣势:Erlang语言栈对国内开发团队运维不友好,单机吞吐量仅为万级,远低于Kafka与RocketMQ,海量消息堆积会严重影响性能,集群横向扩展能力弱。

2.1.3 代码实现

maven核心依赖

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>3.2.4</version>
    <relativePath/>
</parent>
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-amqp</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springdoc</groupId>
        <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
        <version>2.5.0</version>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.18.30</version>
        <scope>provided</scope>
    </dependency>
    <dependency>
        <groupId>com.alibaba.fastjson2</groupId>
        <artifactId>fastjson2</artifactId>
        <version>2.0.48</version>
    </dependency>
    <dependency>
        <groupId>com.google.guava</groupId>
        <artifactId>guava</artifactId>
        <version>33.1.0-jre</version>
    </dependency>
</dependencies>

配置文件application.yml

spring:
  rabbitmq:
    host: 127.0.0.1
    port: 5672
    username: guest
    password: guest
    virtual-host: /
    publisher-confirm-type: correlated
    publisher-returns: true
    listener:
      simple:
        acknowledge-mode: manual
        prefetch: 10

RabbitMQ配置类

package com.jam.demo.config;

import org.springframework.amqp.core.*;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class RabbitMqConfig {

    public static final String DIRECT_EXCHANGE = "demo.direct.exchange";
    public static final String DIRECT_QUEUE = "demo.direct.queue";
    public static final String ROUTING_KEY = "demo.routing.key";
    public static final String DEAD_LETTER_EXCHANGE = "demo.dead.letter.exchange";
    public static final String DEAD_LETTER_QUEUE = "demo.dead.letter.queue";

    @Bean
    public DirectExchange directExchange() {
        return ExchangeBuilder.directExchange(DIRECT_EXCHANGE).durable(true).build();
    }

    @Bean
    public Queue directQueue() {
        return QueueBuilder.durable(DIRECT_QUEUE)
                .withArgument("x-dead-letter-exchange", DEAD_LETTER_EXCHANGE)
                .withArgument("x-dead-letter-routing-key""dead.letter.key")
                .build();
    }

    @Bean
    public Binding bindingDirect() {
        return BindingBuilder.bind(directQueue()).to(directExchange()).with(ROUTING_KEY);
    }

    @Bean
    public DirectExchange deadLetterExchange() {
        return ExchangeBuilder.directExchange(DEAD_LETTER_EXCHANGE).durable(true).build();
    }

    @Bean
    public Queue deadLetterQueue() {
        return QueueBuilder.durable(DEAD_LETTER_QUEUE).build();
    }

    @Bean
    public Binding bindingDeadLetter() {
        return BindingBuilder.bind(deadLetterQueue()).to(deadLetterExchange()).with("dead.letter.key");
    }

    @Bean
    public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {
        RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
        rabbitTemplate.setMandatory(true);
        return rabbitTemplate;
    }
}

消息实体类

package com.jam.demo.entity;

import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;
import java.time.LocalDateTime;

@Data
@NoArgsConstructor
@AllArgsConstructor
@Schema(description = "消息实体")
public class DemoMessage implements Serializable {

    private static final long serialVersionUID = 1L;

    @Schema(description = "消息ID")
    private String messageId;

    @Schema(description = "消息内容")
    private String content;

    @Schema(description = "创建时间")
    private LocalDateTime createTime;
}

消息生产者

package com.jam.demo.producer;

import com.alibaba.fastjson2.JSON;
import com.jam.demo.config.RabbitMqConfig;
import com.jam.demo.entity.DemoMessage;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.stereotype.Component;

/**
 * RabbitMQ消息生产者
 * @author ken
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class RabbitMqProducer {

    private final RabbitTemplate rabbitTemplate;

    /**
     * 发送消息
     * @param message 消息实体
     */
    public void sendMessage(DemoMessage message) {
        rabbitTemplate.convertAndSend(
                RabbitMqConfig.DIRECT_EXCHANGE,
                RabbitMqConfig.ROUTING_KEY,
                JSON.toJSONString(message),
                correlationData -> {
                    correlationData.getMessageProperties().setMessageId(message.getMessageId());
                    return correlationData;
                }
        );
        log.info("消息发送成功,messageId:{}", message.getMessageId());
    }
}

消息消费者

package com.jam.demo.consumer;

import com.alibaba.fastjson2.JSON;
import com.jam.demo.config.RabbitMqConfig;
import com.jam.demo.entity.DemoMessage;
import com.rabbitmq.client.Channel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.support.AmqpHeaders;
import org.springframework.messaging.handler.annotation.Header;
import org.springframework.stereotype.Component;

/**
 * RabbitMQ消息消费者
 * @author ken
 */
@Slf4j
@Component
public class RabbitMqConsumer {

    @RabbitListener(queues = RabbitMqConfig.DIRECT_QUEUE)
    public void handleMessage(String messageStr, Channel channel, @Header(AmqpHeaders.DELIVERY_TAG) long deliveryTag) {
        try {
            DemoMessage message = JSON.parseObject(messageStr, DemoMessage.class);
            log.info("收到消息,messageId:{},内容:{}", message.getMessageId(), message.getContent());
            channel.basicAck(deliveryTag, false);
        } catch (Exception e) {
            log.error("消息处理失败", e);
            channel.basicNack(deliveryTag, falsefalse);
        }
    }

    @RabbitListener(queues = RabbitMqConfig.DEAD_LETTER_QUEUE)
    public void handleDeadLetter(String messageStr, Channel channel, @Header(AmqpHeaders.DELIVERY_TAG) long deliveryTag) {
        log.error("收到死信消息,内容:{}", messageStr);
        try {
            channel.basicAck(deliveryTag, false);
        } catch (Exception e) {
            log.error("死信消息处理失败", e);
        }
    }
}

对外接口层

package com.jam.demo.controller;

import com.jam.demo.entity.DemoMessage;
import com.jam.demo.producer.RabbitMqProducer;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.time.LocalDateTime;
import java.util.UUID;

@RestController
@RequestMapping("/rabbitmq")
@RequiredArgsConstructor
@Tag(name = "RabbitMQ消息接口", description = "RabbitMQ消息发送相关接口")
public class RabbitMqController {

    private final RabbitMqProducer rabbitMqProducer;

    @PostMapping("/send")
    @Operation(summary = "发送消息", description = "发送RabbitMQ消息")
    public String sendMessage(@RequestBody String content) {
        DemoMessage message = new DemoMessage(UUID.randomUUID().toString(), content, LocalDateTime.now());
        rabbitMqProducer.sendMessage(message);
        return "消息发送成功";
    }
}

2.2 RocketMQ:金融级高可靠分布式消息中间件

RocketMQ是阿里开源的Java语言开发的消息中间件,经过双十一万亿级流量考验,凭借金融级高可靠、高吞吐、功能完善的特性,成为国内互联网核心业务的首选。

2.2.1 核心架构与底层原理

RocketMQ的核心架构分为四大组件,各组件职责清晰,支持水平扩展:

  • NameServer:轻量级路由注册中心,负责管理Broker集群的元数据,提供Topic的路由信息,无状态设计,可集群部署,节点之间无数据同步,可用性极高。
  • Broker:消息存储与调度的核心节点,分为Master节点与Slave节点,Master负责消息写入与读取,Slave同步Master的数据,Master故障时Slave可切换为主节点,提供高可用能力。
  • Producer:消息生产者,通过NameServer获取Topic的路由信息,将消息发送至对应的Broker节点,支持同步发送、异步发送、单向发送、事务消息四种发送方式。
  • Consumer:消息消费者,通过NameServer获取Topic的路由信息,从Broker节点拉取消息,支持集群消费与广播消费两种模式,集群消费模式下,一条消息仅被消费组内的一个消费者消费;广播消费模式下,消费组内的所有消费者都会消费这条消息。

2.2.2 核心特性与优劣势

  • 存储机制:采用“CommitLog + ConsumeQueue + IndexFile”的三级存储结构,所有消息均顺序写入CommitLog文件,完全规避随机IO,ConsumeQueue是消息的逻辑消费队列,存储消息在CommitLog中的偏移量,IndexFile为消息索引,支持根据消息ID、Key快速查询消息,通过零拷贝技术提升消息读取性能。
  • 高可用机制:支持主从同步架构与Dledger模式,Dledger模式基于Raft一致性协议实现,保证主从节点的数据一致性,主节点故障时自动选举新的主节点,无需人工干预,实现真正的金融级高可用。
  • 消费模式:采用拉模式,封装了推模式的API,消费者主动从Broker拉取消息,可自主控制消费速度,避免消费者被压垮,支持消息重试、死信队列、消费进度持久化等能力。
  • 核心优势:单机吞吐量可达10万级,仅次于Kafka,金融级高可靠设计,支持完善的事务消息、定时消息、消息轨迹、重试死信机制,Java语言栈对国内开发团队友好,运维成本低,完美适配国内云厂商环境,经过双十一海量流量验证,稳定性极强。
  • 核心劣势:海外生态不如Kafka与RabbitMQ,国际认可度较低,流处理能力弱于Kafka,与大数据生态的集成度不如Kafka。

2.2.3 代码实现

maven核心依赖

<dependencies>
    <dependency>
        <groupId>org.apache.rocketmq</groupId>
        <artifactId>rocketmq-spring-boot-starter</artifactId>
        <version>2.3.0</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
        <version>3.2.4</version>
    </dependency>
    <dependency>
        <groupId>org.springdoc</groupId>
        <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
        <version>2.5.0</version>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.18.30</version>
        <scope>provided</scope>
    </dependency>
    <dependency>
        <groupId>com.alibaba.fastjson2</groupId>
        <artifactId>fastjson2</artifactId>
        <version>2.0.48</version>
    </dependency>
    <dependency>
        <groupId>com.google.guava</groupId>
        <artifactId>guava</artifactId>
        <version>33.1.0-jre</version>
    </dependency>
</dependencies>

配置文件application.yml

rocketmq:
  name-server: 127.0.0.1:9876
  producer:
    group: demo-producer-group
    send-message-timeout: 3000

消息生产者

package com.jam.demo.producer;

import com.alibaba.fastjson2.JSON;
import com.jam.demo.entity.DemoMessage;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.springframework.messaging.Message;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.stereotype.Component;

/**
 * RocketMQ消息生产者
 * @author ken
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class RocketMqProducer {

    private final RocketMQTemplate rocketMQTemplate;
    public static final String TOPIC = "demo-topic";
    public static final String TAG = "demo-tag";

    /**
     * 发送普通消息
     * @param message 消息实体
     */
    public void sendMessage(DemoMessage message) {
        String destination = TOPIC + ":" + TAG;
        Message<String> msg = MessageBuilder.withPayload(JSON.toJSONString(message))
                .setHeader("KEYS", message.getMessageId())
                .build();
        rocketMQTemplate.syncSend(destination, msg);
        log.info("消息发送成功,messageId:{}", message.getMessageId());
    }
}

事务消息生产者

package com.jam.demo.producer;

import com.alibaba.fastjson2.JSON;
import com.jam.demo.entity.DemoMessage;
import com.jam.demo.service.DemoTransactionService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.spring.annotation.RocketMQTransactionListener;
import org.apache.rocketmq.spring.core.RocketMQLocalTransactionListener;
import org.apache.rocketmq.spring.core.RocketMQLocalTransactionState;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.springframework.messaging.Message;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.stereotype.Component;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionTemplate;

/**
 * RocketMQ事务消息生产者
 * @author ken
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class RocketMqTransactionProducer {

    private final RocketMQTemplate rocketMQTemplate;
    private final TransactionTemplate transactionTemplate;
    private final DemoTransactionService demoTransactionService;
    public static final String TRANSACTION_TOPIC = "demo-transaction-topic";

    /**
     * 发送事务消息
     * @param message 消息实体
     */
    public void sendTransactionMessage(DemoMessage message) {
        Message<String> msg = MessageBuilder.withPayload(JSON.toJSONString(message))
                .setHeader("KEYS", message.getMessageId())
                .build();
        rocketMQTemplate.sendMessageInTransaction(TRANSACTION_TOPIC, msg, message.getMessageId());
        log.info("事务半消息发送成功,messageId:{}", message.getMessageId());
    }

    @RocketMQTransactionListener
    @RequiredArgsConstructor
    public static class TransactionListenerImpl implements RocketMQLocalTransactionListener {

        private final TransactionTemplate transactionTemplate;
        private final DemoTransactionService demoTransactionService;

        @Override
        public RocketMQLocalTransactionState executeLocalTransaction(Message msg, Object arg) {
            String messageId = (String) arg;
            try {
                return transactionTemplate.execute(new TransactionCallback<RocketMQLocalTransactionState>() {
                    @Override
                    public RocketMQLocalTransactionState doInTransaction(TransactionStatus status) {
                        try {
                            demoTransactionService.handleLocalTransaction(messageId);
                            return RocketMQLocalTransactionState.COMMIT;
                        } catch (Exception e) {
                            status.setRollbackOnly();
                            log.error("本地事务执行失败,messageId:{}", messageId, e);
                            return RocketMQLocalTransactionState.ROLLBACK;
                        }
                    }
                });
            } catch (Exception e) {
                log.error("本地事务执行异常,messageId:{}", messageId, e);
                return RocketMQLocalTransactionState.UNKNOWN;
            }
        }

        @Override
        public RocketMQLocalTransactionState checkLocalTransaction(Message msg) {
            String messageId = (String) msg.getHeaders().get("KEYS");
            try {
                boolean isSuccess = demoTransactionService.checkTransactionStatus(messageId);
                return isSuccess ? RocketMQLocalTransactionState.COMMIT : RocketMQLocalTransactionState.ROLLBACK;
            } catch (Exception e) {
                log.error("事务回查异常,messageId:{}", messageId, e);
                return RocketMQLocalTransactionState.UNKNOWN;
            }
        }
    }
}

消息消费者

package com.jam.demo.consumer;

import com.alibaba.fastjson2.JSON;
import com.jam.demo.entity.DemoMessage;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
import org.apache.rocketmq.spring.core.RocketMQListener;
import org.springframework.stereotype.Component;

/**
 * RocketMQ普通消息消费者
 * @author ken
 */
@Slf4j
@Component
@RocketMQMessageListener(
        topic = "demo-topic",
        consumerGroup = "demo-consumer-group",
        selectorExpression = "demo-tag"
)
public class RocketMqConsumer implements RocketMQListener<String> {

    @Override
    public void onMessage(String messageStr) {
        DemoMessage message = JSON.parseObject(messageStr, DemoMessage.class);
        log.info("收到消息,messageId:{},内容:{}", message.getMessageId(), message.getContent());
    }
}

事务消息消费者

package com.jam.demo.consumer;

import com.alibaba.fastjson2.JSON;
import com.jam.demo.entity.DemoMessage;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
import org.apache.rocketmq.spring.core.RocketMQListener;
import org.springframework.stereotype.Component;

/**
 * RocketMQ事务消息消费者
 * @author ken
 */
@Slf4j
@Component
@RocketMQMessageListener(
        topic = "demo-transaction-topic",
        consumerGroup = "demo-transaction-consumer-group"
)
public class RocketMqTransactionConsumer implements RocketMQListener<String> {

    @Override
    public void onMessage(String messageStr) {
        DemoMessage message = JSON.parseObject(messageStr, DemoMessage.class);
        log.info("收到事务消息,messageId:{},内容:{}", message.getMessageId(), message.getContent());
    }
}

对外接口层

package com.jam.demo.controller;

import com.jam.demo.entity.DemoMessage;
import com.jam.demo.producer.RocketMqProducer;
import com.jam.demo.producer.RocketMqTransactionProducer;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.time.LocalDateTime;
import java.util.UUID;

@RestController
@RequestMapping("/rocketmq")
@RequiredArgsConstructor
@Tag(name = "RocketMQ消息接口", description = "RocketMQ消息发送相关接口")
public class RocketMqController {

    private final RocketMqProducer rocketMqProducer;
    private final RocketMqTransactionProducer rocketMqTransactionProducer;

    @PostMapping("/send")
    @Operation(summary = "发送普通消息", description = "发送RocketMQ普通消息")
    public String sendMessage(@RequestBody String content) {
        DemoMessage message = new DemoMessage(UUID.randomUUID().toString(), content, LocalDateTime.now());
        rocketMqProducer.sendMessage(message);
        return "消息发送成功";
    }

    @PostMapping("/send-transaction")
    @Operation(summary = "发送事务消息", description = "发送RocketMQ事务消息")
    public String sendTransactionMessage(@RequestBody String content) {
        DemoMessage message = new DemoMessage(UUID.randomUUID().toString(), content, LocalDateTime.now());
        rocketMqTransactionProducer.sendTransactionMessage(message);
        return "事务消息发送成功";
    }
}

事务服务接口与实现

package com.jam.demo.service;

/**
 * 事务演示服务接口
 * @author ken
 */
public interface DemoTransactionService {

    /**
     * 执行本地事务
     * @param messageId 消息ID
     */
    void handleLocalTransaction(String messageId);

    /**
     * 检查本地事务状态
     * @param messageId 消息ID
     * @return 事务是否执行成功
     */
    boolean checkTransactionStatus(String messageId);
}
package com.jam.demo.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.jam.demo.entity.TransactionLog;
import com.jam.demo.mapper.TransactionLogMapper;
import com.jam.demo.service.DemoTransactionService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.util.ObjectUtils;

import java.time.LocalDateTime;

/**
 * 事务演示服务实现类
 * @author ken
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class DemoTransactionServiceImpl implements DemoTransactionService {

    private final TransactionLogMapper transactionLogMapper;

    @Override
    public void handleLocalTransaction(String messageId) {
        TransactionLog transactionLog = new TransactionLog();
        transactionLog.setMessageId(messageId);
        transactionLog.setCreateTime(LocalDateTime.now());
        transactionLogMapper.insert(transactionLog);
        log.info("本地事务执行成功,messageId:{}", messageId);
    }

    @Override
    public boolean checkTransactionStatus(String messageId) {
        LambdaQueryWrapper<TransactionLog> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(TransactionLog::getMessageId, messageId);
        TransactionLog transactionLog = transactionLogMapper.selectOne(queryWrapper);
        return !ObjectUtils.isEmpty(transactionLog);
    }
}

事务日志实体与Mapper

package com.jam.demo.entity;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;

import java.time.LocalDateTime;

@Data
@TableName("t_transaction_log")
public class TransactionLog {

    @TableId(type = IdType.AUTO)
    private Long id;

    private String messageId;

    private LocalDateTime createTime;
}
package com.jam.demo.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.jam.demo.entity.TransactionLog;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface TransactionLogMapper extends BaseMapper<TransactionLog> {
}

MySQL建表语句

CREATE TABLE `t_transaction_log` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
  `message_id` varchar(64NOT NULL COMMENT '消息ID',
  `create_time` datetime NOT NULL COMMENT '创建时间',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_message_id` (`message_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='事务日志表';

2.3 Kafka:高吞吐低延迟的分布式流处理平台

Kafka是由Scala与Java语言开发的分布式流处理平台,凭借极致的吞吐性能、低延迟、海量数据存储能力,成为大数据与实时流处理场景的绝对主流。

2.3.1 核心架构与底层原理

Kafka的核心架构围绕Topic与Partition展开,2.8版本后推出KRaft模式,替代了传统的ZooKeeper依赖,架构更简洁,可用性更高:

  • Producer:消息生产者,负责将消息发送至Topic的对应Partition,支持消息批量发送、压缩、分区路由、幂等发送与事务消息。
  • Broker:Kafka的服务节点,负责存储消息、处理生产与消费请求、副本同步,一个Kafka集群由多个Broker节点组成,支持水平扩展。
  • Controller:集群控制器,负责管理集群的元数据、Partition的Leader选举、副本分配等,KRaft模式下,通过Raft协议选举Controller节点,无需ZooKeeper。
  • Topic:消息主题,是消息的逻辑分类,一个Topic可以分为多个Partition,Partition是Kafka消息存储与消费的最小单元,实现了消息的分布式存储与并行消费。
  • Replica:Partition的副本,分为Leader副本与Follower副本,Leader副本负责处理生产与消费请求,Follower副本同步Leader副本的数据,Leader故障时,从ISR集合中的Follower副本选举新的Leader。
  • Consumer Group:消费者组,多个消费者组成一个消费组,一个Partition只能被消费组内的一个消费者消费,实现了消费的负载均衡与水平扩展,不同消费组之间互不影响,可独立消费同一个Topic的消息。
  • Offset:消息的偏移量,是消息在Partition中的唯一标识,消费者通过记录Offset来标记消费进度,保证故障重启后可从上次消费的位置继续消费。

2.3.2 核心特性与优劣势

  • 存储机制:采用分段日志存储结构,每个Partition对应多个Segment文件,每个Segment包含一个数据文件与两个索引文件(偏移量索引与时间戳索引),所有消息均顺序写入数据文件,完全规避随机IO,通过mmap与sendfile两种零拷贝技术,实现消息的极致读写性能,支持海量消息堆积,消息堆积对性能的影响极小。
  • 高可用机制:基于ISR(In-Sync Replicas)同步副本集合实现高可用,只有与Leader副本保持同步的Follower副本才能进入ISR集合,Leader故障时,仅ISR集合中的副本有资格被选举为新的Leader,保证数据不丢失,KRaft模式下,基于Raft协议实现集群元数据的一致性,无需依赖ZooKeeper,架构更稳定,故障恢复速度更快。
  • 消费模式:纯拉模式,消费者主动从Broker拉取消息,可完全自主控制消费速度、消费位置与消费时机,支持回溯消费、重复消费、批量消费,灵活性极高。
  • 核心优势:单机吞吐量可达10万级以上,是三大消息队列中最高的,延迟可控制在毫秒级,支持海量消息堆积,与大数据生态(Flink、Spark、Hadoop)完美集成,流处理能力极强,国际认可度极高,生态极其完善,经过全球海量互联网公司的生产验证,稳定性极强。
  • 核心劣势:功能相对单一,不支持复杂的路由规则,事务消息能力弱于RocketMQ,不支持定时消息、消息重试的灵活配置,企业级特性不如RocketMQ与RabbitMQ完善,运维复杂度较高,对团队的技术能力要求较高。

2.3.3 代码实现

maven核心依赖

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-kafka</artifactId>
        <version>3.2.4</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
        <version>3.2.4</version>
    </dependency>
    <dependency>
        <groupId>org.springdoc</groupId>
        <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
        <version>2.5.0</version>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.18.30</version>
        <scope>provided</scope>
    </dependency>
    <dependency>
        <groupId>com.alibaba.fastjson2</groupId>
        <artifactId>fastjson2</artifactId>
        <version>2.0.48</version>
    </dependency>
    <dependency>
        <groupId>com.google.guava</groupId>
        <artifactId>guava</artifactId>
        <version>33.1.0-jre</version>
    </dependency>
</dependencies>

配置文件application.yml

spring:
  kafka:
    bootstrap-servers: 127.0.0.1:9092
    producer:
      key-serializer: org.apache.kafka.common.serialization.StringSerializer
      value-serializer: org.apache.kafka.common.serialization.StringSerializer
      acks: 1
      retries: 3
      batch-size: 16384
      buffer-memory: 33554432
    consumer:
      key-deserializer: org.apache.kafka.common.serialization.StringDeserializer
      value-deserializer: org.apache.kafka.common.serialization.StringDeserializer
      group-id: demo-consumer-group
      auto-offset-reset: earliest
      enable-auto-commit: false
    listener:
      ack-mode: manual_immediate

消息生产者

package com.jam.demo.producer;

import com.alibaba.fastjson2.JSON;
import com.jam.demo.entity.DemoMessage;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.kafka.support.SendResult;
import org.springframework.stereotype.Component;

import java.util.concurrent.CompletableFuture;

/**
 * Kafka消息生产者
 * @author ken
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class KafkaProducer {

    private final KafkaTemplate<StringString> kafkaTemplate;
    public static final String TOPIC = "demo-topic";

    /**
     * 发送消息
     * @param message 消息实体
     */
    public void sendMessage(DemoMessage message) {
        String messageStr = JSON.toJSONString(message);
        CompletableFuture<SendResult<StringString>> future = kafkaTemplate.send(TOPIC, message.getMessageId(), messageStr);
        future.whenComplete((result, ex) -> {
            if (ex == null) {
                log.info("消息发送成功,messageId:{},offset:{}", message.getMessageId(), result.getRecordMetadata().offset());
            } else {
                log.error("消息发送失败,messageId:{}", message.getMessageId(), ex);
            }
        });
    }
}

消息消费者

package com.jam.demo.consumer;

import com.alibaba.fastjson2.JSON;
import com.jam.demo.entity.DemoMessage;
import lombok.extern.slf4j.Slf4j;
import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.springframework.kafka.annotation.KafkaListener;
import org.springframework.kafka.support.Acknowledgment;
import org.springframework.stereotype.Component;

/**
 * Kafka消息消费者
 * @author ken
 */
@Slf4j
@Component
public class KafkaConsumer {

    @KafkaListener(topics = "demo-topic", groupId = "demo-consumer-group")
    public void handleMessage(ConsumerRecord<StringString> record, Acknowledgment ack) {
        try {
            DemoMessage message = JSON.parseObject(record.value(), DemoMessage.class);
            log.info("收到消息,messageId:{},offset:{},内容:{}", message.getMessageId(), record.offset(), message.getContent());
            ack.acknowledge();
        } catch (Exception e) {
            log.error("消息处理失败,offset:{}", record.offset(), e);
        }
    }
}

对外接口层

package com.jam.demo.controller;

import com.jam.demo.entity.DemoMessage;
import com.jam.demo.producer.KafkaProducer;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.time.LocalDateTime;
import java.util.UUID;

@RestController
@RequestMapping("/kafka")
@RequiredArgsConstructor
@Tag(name = "Kafka消息接口", description = "Kafka消息发送相关接口")
public class KafkaController {

    private final KafkaProducer kafkaProducer;

    @PostMapping("/send")
    @Operation(summary = "发送消息", description = "发送Kafka消息")
    public String sendMessage(@RequestBody String content) {
        DemoMessage message = new DemoMessage(UUID.randomUUID().toString(), content, LocalDateTime.now());
        kafkaProducer.sendMessage(message);
        return "消息发送成功";
    }
}

三、三大MQ核心维度横向对比与易混淆点区分

3.1 核心维度横向对比

对比维度RabbitMQRocketMQKafka
开发语言ErlangJavaScala/Java
核心协议AMQP自定义协议自定义协议
单机TPS峰值万级10万级10万级以上
消息延迟微秒级毫秒级毫秒级
消息可靠性极高,支持同步刷盘极高,支持同步刷盘与Raft一致性高,依赖ISR集合与副本机制
功能丰富度极高,支持灵活路由、优先级队列、延迟队列等极高,支持事务消息、定时消息、消息轨迹等中等,核心聚焦消息投递与流处理
集群扩展能力弱,镜像队列模式扩展成本高强,支持水平扩展,节点扩容无感知极强,支持水平扩展,Partition可动态扩容
运维复杂度中等,Erlang语言栈运维门槛高低,Java语言栈,国内文档丰富高,对团队技术能力要求高
大数据生态适配中等极强,完美适配所有大数据组件
学习成本低,文档完善,API简单中等,功能丰富,国内文档齐全中等,核心概念多,学习曲线较陡

3.2 易混淆技术点深度区分

3.2.1 推模式 vs 拉模式

  • 推模式:RabbitMQ的默认模式,Broker主动将消息推送至消费者,优势是消息延迟极低,消费者无需轮询,劣势是无法控制消费速度,消费者处理能力不足时会被消息压垮。
  • 拉模式:Kafka与RocketMQ的核心模式,消费者主动从Broker拉取消息,优势是可自主控制消费速度,适配不同处理能力的消费者,灵活性极高,劣势是消息延迟略高于推模式,需要轮询请求。

3.2.2 事务消息实现差异

  • RabbitMQ:基于AMQP协议的事务机制,采用channel.txSelect()、channel.txCommit()、channel.txRollback()实现,仅能保证本地事务与消息发送的原子性,不支持分布式事务的回查机制,功能极弱。
  • RocketMQ:采用两阶段提交的事务消息机制,先发送半消息,半消息对消费者不可见,本地事务执行成功后提交消息,执行失败回滚消息,支持事务回查机制,解决本地事务执行异常导致的消息状态不一致问题,是目前最完善的事务消息实现。
  • Kafka:基于幂等生产者与事务API实现,仅能保证生产者发送的多条消息的原子性,以及消费与生产的原子性,不支持本地事务与消息发送的原子性,无法实现事务回查,仅适用于流处理场景的事务保证。

3.2.3 高可用机制差异

  • RabbitMQ:镜像队列模式,将队列的全量数据同步至所有镜像节点,同步成本极高,集群扩展能力弱,主节点故障时自动切换从节点,数据一致性高,但性能损耗大。
  • RocketMQ:Dledger模式基于Raft一致性协议,保证主从节点的数据强一致性,主节点故障时自动选举新的主节点,故障恢复速度快,数据一致性高,性能损耗可控,支持水平扩展。
  • Kafka:基于ISR同步副本集合,只有与Leader保持同步的副本才能参与选举,保证数据不丢失,KRaft模式基于Raft协议实现集群元数据的一致性,故障恢复速度极快,支持大规模集群的水平扩展。

四、场景化选型最佳实践

没有最优的消息队列,只有最适配业务场景的选择,基于三大MQ的核心特性,给出明确的选型结论:

4.1 首选RabbitMQ的场景

  • 中小企业内部业务系统,需求轻量,需要快速部署、开箱即用的消息中间件。
  • 业务需要复杂的路由规则,比如多租户隔离、多条件匹配的消息分发场景。
  • 对消息可靠性要求高,但吞吐量要求不高的企业级应用,比如OA、CRM、ERP等内部系统。
  • 需要延迟队列、消息优先级、死信队列等轻量级高级特性,且不想引入过重的中间件的场景。

4.2 首选RocketMQ的场景

  • 国内互联网核心业务,比如电商交易、订单支付、物流调度等金融级高可靠场景。
  • 对消息可靠性、一致性要求极高,同时需要支撑高并发、高吞吐的业务场景。
  • 业务需要丰富的消息功能,比如事务消息、定时消息、消息轨迹、灵活的重试死信机制。
  • 团队技术栈以Java为主,需要低运维成本、完善的国内文档与社区支持的场景。

4.3 首选Kafka的场景

  • 日志采集、监控数据上报、用户行为埋点等超高吞吐、海量数据的采集场景。
  • 实时流处理、大数据分析场景,需要与Flink、Spark Streaming等大数据组件深度集成。
  • 国际化业务、海外项目,需要极高的生态适配度与国际社区支持的场景。
  • 需要支撑海量消息堆积,且消息堆积不能影响系统性能的场景。

五、通用最佳实践与避坑指南

5.1 生产端最佳实践

  • 消息体尽量精简,避免大消息,大消息会严重影响消息队列的性能,超过1MB的消息建议采用对象存储+消息通知的方式处理。
  • 合理使用批量发送,提升吞吐量,但需控制批量消息的大小,避免单批次消息过大导致超时。
  • 生产端必须添加消息发送失败的重试机制,同时设置合理的重试次数与重试间隔,避免无限重试。
  • 所有消息必须设置唯一的业务标识,用于消费端的幂等处理与问题排查。

5.2 消费端最佳实践

  • 所有消费场景必须实现幂等处理,三大MQ均保证至少一次投递语义,消息重复不可避免,幂等是消费端的必备能力。
  • 合理设置消费并行度,消费线程数与Topic的Partition/Queue数量匹配,避免并行度过高导致的频繁上下文切换。
  • 消费失败必须设置合理的重试策略,避免无限重试导致的消费阻塞,无法处理的消息必须转入死信队列,人工干预处理。
  • 禁止在消费逻辑中执行远程调用、数据库操作等耗时过长的操作,避免消费速度过慢导致的消息堆积。

5.3 通用避坑指南

  • 禁止使用消息队列做同步RPC调用,消息队列的核心价值是异步解耦,同步调用会完全丧失消息队列的优势,且引入极高的复杂度。
  • 必须做好监控告警,核心监控指标包括:生产端发送成功率、消息堆积量、消费成功率、死信队列消息数量、Broker节点的磁盘与内存使用率。
  • 合理设置消息的过期时间,避免无效消息长期占用磁盘空间,导致磁盘使用率过高。
  • 集群容量规划必须预留足够的冗余,避免大促、流量突增时集群性能不足导致的系统雪崩。