一、消息队列核心底层原理
消息队列是分布式系统的核心基础设施,其本质是以异步通信为核心,实现系统解耦、流量削峰与数据分发的中间件。想要做好选型,必须先吃透其通用底层逻辑,避免陷入“只看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, false, false);
}
}
@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(64) NOT 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<String, String> kafkaTemplate;
public static final String TOPIC = "demo-topic";
/**
* 发送消息
* @param message 消息实体
*/
public void sendMessage(DemoMessage message) {
String messageStr = JSON.toJSONString(message);
CompletableFuture<SendResult<String, String>> 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<String, String> 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 核心维度横向对比
| 对比维度 | RabbitMQ | RocketMQ | Kafka |
|---|---|---|---|
| 开发语言 | Erlang | Java | Scala/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节点的磁盘与内存使用率。
- 合理设置消息的过期时间,避免无效消息长期占用磁盘空间,导致磁盘使用率过高。
- 集群容量规划必须预留足够的冗余,避免大促、流量突增时集群性能不足导致的系统雪崩。