消息队列Kafka与RabbitMQ深度解析:把分布式消息核心讲透,吊打面试官
🎯 写在前面:在分布式系统中,消息队列是解耦、削峰、异步通信的核心组件。Kafka和RabbitMQ是最主流的两大消息队列框架。但你真的了解它们的底层原理吗?这篇文章,将带你深度剖析Kafka与RabbitMQ!
一、核心概念:消息队列基础
1.1 为什么需要消息队列?
css
┌─────────────────────────────────────────────────────────────────────┐
│ 消息队列核心价值 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ 1. 解耦 │ │
│ │ │ │
│ │ 系统A ──────────────────────→ 系统B │ │
│ │ 系统A ──────── MQ ────────────→ 系统B、C、D │ │
│ │ 系统A无需知道谁在消费 │ │
│ └────────────────────────────────────────────────────────────────┘ │
│ │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ 2. 异步 │ │
│ │ │ │
│ │ 同步:用户下单 → 10ms → 库存扣减 → 50ms → 支付 → 100ms │ │
│ │ 异步:用户下单 → MQ → 返回成功 │ │
│ │ 后台:库存扣减(异步) + 支付(异步) + 发货通知(异步) │ │
│ └────────────────────────────────────────────────────────────────┘ │
│ │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ 3. 削峰 │ │
│ │ │ │
│ │ 突发流量:10000 QPS → 写入MQ → 后端:100 QPS平稳消费 │ │
│ │ 保护系统不被冲垮 │ │
│ └────────────────────────────────────────────────────────────────┘ │
│ │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ 4. 广播 │ │
│ │ │ │
│ │ 一次发布,多个消费者订阅 │ │
│ │ 例:订单创建 → 短信通知 + 邮件通知 + 物流更新 + 数据分析 │ │
│ └────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────┘
1.2 Kafka vs RabbitMQ对比
scss
┌─────────────────────────────────────────────────────────────────────┐
│ Kafka vs RabbitMQ 对比 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────────────┬──────────────────────────────────────────┐│
│ │ Kafka │ RabbitMQ ││
│ ├──────────────────────┼──────────────────────────────────────────┤│
│ │ 架构:日志追加 │ 架构:队列 + 交换机 ││
│ │ 存储:分布式日志 │ 存储:内存 + 持久化 ││
│ │ 消费:拉取(Pull) │ 消费:推送(Push) ││
│ │ 顺序:分区有序 │ 顺序:单队列有序 ││
│ │ 事务:支持 │ 事务:支持 ││
│ │ 延迟消息:不支持 │ 延迟消息:支持(插件) ││
│ │ 优先级队列:不支持 │ 优先级队列:支持 ││
│ │ 消息量级:万亿级 │ 消息量级:千万级 ││
│ │ 吞吐量:百万级QPS │ 吞吐量:万级QPS ││
│ │ 消息回溯:不支持 │ 消息回溯:支持 ││
│ │ 适用场景:日志、大数据│ 适用场景:业务消息、RPC ││
│ └──────────────────────┴──────────────────────────────────────────┘│
│ │
│ 选型建议: │
│ ✅ 日志采集、大数据流处理 → Kafka │
│ ✅ 业务消息、延迟任务、复杂路由 → RabbitMQ │
│ ✅ 追求高性能、大数据量 → Kafka │
│ ✅ 追求可靠性、事务支持 → RabbitMQ │
│ │
└─────────────────────────────────────────────────────────────────────┘
二、Kafka深度剖析
2.1 核心架构
css
┌─────────────────────────────────────────────────────────────────────┐
│ Kafka核心架构 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ ZooKeeper/KRaft │ │
│ │ 元数据管理、分区分配、Leader选举 │ │
│ └────────────────────────────────────────────────────────────────┘ │
│ ↓ │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ Kafka Cluster │ │
│ │ │ │
│ │ ┌──────────────────────────────────────────────────────────┐ │ │
│ │ │ Topic: order │ │ │
│ │ │ │ │ │
│ │ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │ │
│ │ │ │Partition│ │Partition│ │Partition│ │ │ │
│ │ │ │ 0 │ │ 1 │ │ 2 │ │ │ │
│ │ │ │ │ │ │ │ │ │ │ │
│ │ │ │ P0(R) │ │ P1(L) │ │ P2(R) │ │ │ │
│ │ │ │ Node1 │ │ Node2 │ │ Node3 │ │ │ │
│ │ │ │ │ │ │ │ │ │ │ │
│ │ │ │ P0(L) │ │ P1(R) │ │ P2(R) │ │ │ │
│ │ │ │ Node2 │ │ Node3 │ │ Node1 │ │ │ │
│ │ │ └─────────┘ └─────────┘ └─────────┘ │ │ │
│ │ │ ↑ ↑ ↑ │ │ │
│ │ │ └─────────────┴─────────────┘ │ │ │
│ │ │ Consumer Group │ │ │
│ │ └──────────────────────────────────────────────────────────┘ │ │
│ └────────────────────────────────────────────────────────────────┘ │
│ │
│ L = Leader(主副本) R = Follower(从副本) │
│ │
└─────────────────────────────────────────────────────────────────────┘
2.2 消息存储机制
java
/**
* Kafka消息存储结构
*/
// 1. Topic → Partition → Segment
/**
* Segment = .log文件 + .index文件 + .timeindex文件
*
* 消息存储路径:/data/kafka/topics/order-0/000000000.log
*/
// 2. 消息格式
public class KafkaMessage {
// Kafka存储的消息结构
long offset; // 消息偏移量(全局唯一)
int messageSize; // 消息大小
long timestamp; // 时间戳
bytes key; // 消息键(用于分区)
bytes value; // 消息内容
Headers headers; // 消息头
int partition; // 分区号
int crc; // 校验码
}
// 3. 索引机制
/**
* .index:偏移量索引(快速定位消息)
* .timeindex:时间戳索引(按时间范围查询)
*
* 稀疏索引:不是每条消息都建索引,默认每间隔4096字节建一条索引
*/
// 4. 日志清理策略
public class LogCleanupPolicy {
// delete(默认):删除超过保留期的消息
// compact:只保留每个key的最新消息(用于状态存储)
}
2.3 生产者核心代码
java
// Kafka生产者配置与代码
public class KafkaProducerDemo {
public static void main(String[] args) {
// 1. 配置生产者
Properties props = new Properties();
props.put("bootstrap.servers", "localhost:9092");
props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer");
props.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer");
// 可靠性配置
props.put("acks", "all"); // 所有副本确认
props.put("retries", 3); // 重试次数
props.put("enable.idempotence", true); // 幂等性
props.put("max.in.flight.requests.per.connection", 5);
// 性能配置
props.put("batch.size", 16384); // 批量大小(16KB)
props.put("linger.ms", 10); // 等待时间
props.put("buffer.memory", 33554432); // 32MB
// 2. 创建生产者
KafkaProducer<String, String> producer = new KafkaProducer<>(props);
try {
// 3. 发送消息 - 异步发送
ProducerRecord<String, String> record =
new ProducerRecord<>("order-topic", "order-123", "{\"amount\":100}");
producer.send(record, (metadata, exception) -> {
if (exception != null) {
System.err.println("发送失败:" + exception.getMessage());
} else {
System.out.println("发送成功:" +
"partition=" + metadata.partition() +
", offset=" + metadata.offset());
}
});
// 4. 同步发送
ProducerRecord<String, String> record2 =
new ProducerRecord<>("order-topic", "order-456", "{\"amount\":200}");
RecordMetadata metadata = producer.send(record2).get();
System.out.println("同步发送成功:" + metadata.offset());
} finally {
producer.close();
}
}
}
2.4 消费者核心代码
java
// Kafka消费者配置与代码
public class KafkaConsumerDemo {
public static void main(String[] args) {
// 1. 配置消费者
Properties props = new Properties();
props.put("bootstrap.servers", "localhost:9092");
props.put("group.id", "order-consumer-group"); // 消费者组
props.put("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
props.put("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
// 消费配置
props.put("auto.offset.reset", "earliest"); // earliest/latest
props.put("enable.auto.commit", false); // 手动提交偏移量
props.put("max.poll.records", 500); // 每次拉取消息数
props.put("session.timeout.ms", 30000);
// 2. 创建消费者
KafkaConsumer<String, String> consumer = new KafkaConsumer<>(props);
// 3. 订阅主题
consumer.subscribe(Arrays.asList("order-topic", "payment-topic"));
// 或者使用正则订阅
// consumer.subscribe(Pattern.compile("order.*"));
try {
// 4. 拉取消息
while (true) {
ConsumerRecords<String, String> records =
consumer.poll(Duration.ofMillis(100));
for (ConsumerRecord<String, String> record : records) {
System.out.println("消费消息:" +
"topic=" + record.topic() +
", partition=" + record.partition() +
", offset=" + record.offset() +
", key=" + record.key() +
", value=" + record.value());
// 业务处理
processMessage(record.value());
}
// 5. 手动提交偏移量
consumer.commitSync();
}
} finally {
consumer.close();
}
}
private static void processMessage(String message) {
// 业务逻辑处理
}
}
三、RabbitMQ深度剖析
3.1 核心架构
scss
┌─────────────────────────────────────────────────────────────────────┐
│ RabbitMQ核心架构 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ Producer │
│ ↓ │
│ ┌─────────────┐ │
│ │ Exchange │ ← 交换机(路由规则) │
│ │ (Type/RoutingKey)│ │
│ └──────┬──────┘ │
│ ↓ │
│ ┌────────────────┼────────────────┐ │
│ ↓ ↓ ↓ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ Queue1 │ │ Queue2 │ │ Queue3 │ │
│ │ (FIFO) │ │ (FIFO) │ │ (FIFO) │ │
│ └────┬─────┘ └────┬─────┘ └────┬─────┘ │
│ ↓ ↓ ↓ │
│ └────────────────┴────────────────┘ │
│ ↓ │
│ Consumer │
│ │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ Exchange类型 │ │
│ │ │ │
│ │ direct:完全匹配RoutingKey │ │
│ │ fanout:广播给所有绑定的队列 │ │
│ │ topic:通配符匹配(*匹配一个词,#匹配零个或多个) │ │
│ │ headers:匹配消息头的键值对 │ │
│ └────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────┘
3.2 交换机与队列配置
java
// RabbitMQ配置类
@Configuration
public class RabbitMQConfig {
public static final String EXCHANGE_NAME = "order.exchange";
public static final String QUEUE_NAME = "order.queue";
public static final String ROUTING_KEY = "order.created";
// 1. 创建交换机
@Bean
public DirectExchange orderExchange() {
return new DirectExchange(EXCHANGE_NAME, true, false);
}
// 2. 创建队列
@Bean
public Queue orderQueue() {
return QueueBuilder.durable(QUEUE_NAME)
.withArgument("x-dead-letter-exchange", "dlx.exchange") // 死信交换机
.withArgument("x-dead-letter-routing-key", "dlx.order")
.build();
}
// 3. 绑定交换机和队列
@Bean
public Binding orderBinding() {
return BindingBuilder
.bind(orderQueue())
.to(orderExchange())
.with(ROUTING_KEY);
}
}
// 发送消息
@Service
public class OrderServiceImpl {
@Autowired
private RabbitTemplate rabbitTemplate;
public void sendOrder(Order order) {
// 方式1:发送消息
rabbitTemplate.convertAndSend(
RabbitMQConfig.EXCHANGE_NAME,
RabbitMQConfig.ROUTING_KEY,
order
);
// 方式2:发送消息并指定消息属性
MessageProperties props = new MessageProperties();
props.setDeliveryMode(MessageDeliveryMode.PERSISTENT);
props.setExpiration("30000"); // 30秒TTL
props.setPriority(5); // 优先级
Message message = new Message(order.toString().getBytes(), props);
rabbitTemplate.send(message);
}
}
// 消费消息
@Component
public class OrderConsumer {
@RabbitListener(queues = RabbitMQConfig.QUEUE_NAME)
public void handleOrder(Order order, Message message, Channel channel)
throws IOException {
long deliveryTag = message.getMessageProperties().getDeliveryTag();
try {
// 业务处理
processOrder(order);
// 确认消息
channel.basicAck(deliveryTag, false);
} catch (Exception e) {
// 处理失败,重试或拒绝
if (isRetryable(e)) {
// 重试
channel.basicNack(deliveryTag, false, true);
} else {
// 进入死信队列
channel.basicNack(deliveryTag, false, false);
}
}
}
}
3.3 延迟消息实现
java
// 方式1:TTL + 死信队列(简单延迟)
@Configuration
public class DelayQueueConfig {
public static final String DELAY_EXCHANGE = "delay.exchange";
public static final String DELAY_QUEUE = "delay.queue";
public static final String NORMAL_QUEUE = "normal.queue";
@Bean
public CustomExchange delayExchange() {
// x-delayed-message 需要rabbitmq_delayed_message_exchange插件
Map<String, Object> args = new HashMap<>();
args.put("x-delayed-type", "direct");
return new CustomExchange(DELAY_EXCHANGE, "x-delayed-message", true, false, args);
}
@Bean
public Queue delayQueue() {
return QueueBuilder.durable(DELAY_QUEUE).build();
}
@Bean
public Binding delayBinding() {
return BindingBuilder
.bind(delayQueue())
.to(delayExchange())
.with("delay.key")
.noargs();
}
}
// 发送延迟消息(延迟10秒)
@Service
public class DelayMessageService {
@Autowired
private RabbitTemplate rabbitTemplate;
public void sendDelayMessage(Order order, long delayMillis) {
rabbitTemplate.convertAndSend(
DelayQueueConfig.DELAY_EXCHANGE,
"delay.key",
order,
message -> {
message.getMessageProperties().setDelay((int) delayMillis);
return message;
}
);
}
}
四、消息可靠性:核心挑战
4.1 消息可靠性保障机制
arduino
┌─────────────────────────────────────────────────────────────────────┐
│ 消息可靠性保障体系 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ 生产者 ─────────────→ Broker ─────────────→ 消费者 │
│ │
│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │
│ │ 生产者确认 │ │ 消息持久化 │ │ 手动确认 │ │
│ │ │ │ │ │ │ │
│ │ publisher │ │ synchronous │ │ basic.ack │ │
│ │ confirms │ │ flush │ │ │ │
│ │ │ │ │ │ auto ack vs │ │
│ │ Transaction │ │ Page Cache │ │ manual ack │ │
│ └─────────────────┘ └─────────────────┘ └─────────────────┘ │
│ │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ 可靠性级别 │ │
│ │ │ │
│ │ at-most-once:最多一次(可能丢消息) │ │
│ │ at-least-once:至少一次(可能重复消费)← 业务幂等性必须 │ │
│ │ exactly-once:精确一次(代价高昂) │ │
│ └────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────┘
4.2 Kafka可靠性配置
java
// Kafka可靠性配置
public class KafkaReliabilityConfig {
public static Properties getReliableConfig() {
Properties props = new Properties();
// 1. 生产者可靠性
props.put("acks", "all"); // 所有ISR副本确认
props.put("retries", 3); // 重试次数
props.put("enable.idempotence", true); // 幂等性(防止重复发送)
// 2. 消息持久化
props.put("batch.size", 16384); // 批量大小
props.put("linger.ms", 5); // 批次等待时间
props.put("buffer.memory", 33554432); // 32MB缓冲区
// 3. 消费者可靠性
props.put("auto.offset.reset", "earliest"); // earliest/latest
props.put("enable.auto.commit", false); // 手动提交偏移量
// 4. Broker配置
// replication.factor >= 3
// min.insync.replicas = 2
return props;
}
}
// 消费者手动提交偏移量
public class ReliableConsumer {
public void consumeReliable() {
KafkaConsumer<String, String> consumer = new KafkaConsumer<>(props);
consumer.subscribe(Arrays.asList("order-topic"));
while (true) {
ConsumerRecords<String, String> records = consumer.poll(Duration.ofMillis(100));
// 批量处理
List<Order> orders = new ArrayList<>();
for (ConsumerRecord<String, String> record : records) {
Order order = parseOrder(record.value());
orders.add(order);
}
// 业务处理
if (processOrders(orders)) {
// 业务处理成功,提交偏移量
consumer.commitSync();
} else {
// 业务处理失败,不提交偏移量,等待重试
}
}
}
}
4.3 幂等性处理
java
// 消息幂等性处理方案
@Service
public class IdempotentService {
// 方案1:数据库唯一索引
@Autowired
private OrderMapper orderMapper;
public void processOrderWithUniqueIndex(Order order) {
try {
// 使用唯一索引防止重复插入
orderMapper.insert(order);
} catch (DuplicateKeyException e) {
// 重复消息,忽略
log.warn("重复消息: {}", order.getOrderId());
}
}
// 方案2:Redis缓存去重
@Autowired
private RedisTemplate<String, String> redisTemplate;
public void processOrderWithRedis(Order order) {
String key = "order:processed:" + order.getOrderId();
Boolean success = redisTemplate.opsForValue()
.setIfAbsent(key, "1", Duration.ofHours(24));
if (Boolean.TRUE.equals(success)) {
// 首次处理
doProcess(order);
} else {
// 重复消息
log.warn("重复消息: {}", order.getOrderId());
}
}
// 方案3:状态机幂等
@Transactional
public void processOrderWithStateMachine(Order order) {
// 只有PENDING状态才能转为PROCESSING
int rows = orderMapper.updateStatusIfCurrent(
order.getOrderId(),
OrderStatus.PENDING,
OrderStatus.PROCESSING
);
if (rows > 0) {
// 首次处理成功
doProcess(order);
} else {
// 状态已变更,可能是重复消息
log.warn("状态已变更,忽略: {}", order.getOrderId());
}
}
}
五、消息顺序保障
5.1 消息顺序问题
sql
┌─────────────────────────────────────────────────────────────────────┐
│ 消息顺序问题 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ Kafka顺序保证: │
│ - 单分区有序:一个Partition内的消息有序 │
│ - 全局无序:不同Partition之间无法保证顺序 │
│ │
│ 问题场景: │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ 消息1:创建订单 │ │
│ │ 消息2:支付订单(依赖订单创建) │ │
│ │ 消息3:取消订单 │ │
│ │ │ │
│ │ 问题:如果消费顺序是 2 → 1 → 3,支付在创建之前,会导致错误! │ │
│ └────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────┘
5.2 保证顺序的解决方案
java
// 方案1:使用单个Partition(Kafka)
public class SinglePartitionProducer {
public void sendOrderedMessages(List<OrderMessage> messages) {
KafkaProducer<String, String> producer = new KafkaProducer<>(props);
for (OrderMessage msg : messages) {
// 使用相同key保证发送到同一Partition
ProducerRecord<String, String> record =
new ProducerRecord<>(
"order-topic",
msg.getOrderId(), // 相同orderId → 相同Partition
msg.toString()
);
producer.send(record);
}
}
}
// 方案2:顺序消息队列(RabbitMQ)
/**
* RabbitMQ单队列天然保证FIFO顺序
* 只需要:
* 1. 一个消费者串行处理
* 2. 或者使用独占队列
*/
// 方案3:多阶段顺序处理
public class MultiStageOrderProcessor {
// 创建订单阶段
@RabbitListener(queues = "order.create.queue")
public void processCreate(Order order) {
orderMapper.create(order);
// 发送支付消息
rabbitTemplate.convertAndSend("order.pay.exchange", "pay", order);
}
// 支付阶段
@RabbitListener(queues = "order.pay.queue")
public void processPay(Order order) {
// 先检查订单状态
Order current = orderMapper.findById(order.getId());
if (current.getStatus() != OrderStatus.CREATED) {
log.warn("订单状态异常,跳过支付:{}", order.getId());
return;
}
orderMapper.pay(order);
}
}
六、常见面试题
Q1:如何保证消息不丢失?
ini
┌─────────────────────────────────────────────────────────────────────┐
│ 消息不丢失保障措施 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ 生产者侧: │
│ ✅ acks=all(所有副本确认) │
│ ✅ enable.idempotence=true(幂等发送) │
│ ✅ retries=3(失败重试) │
│ ✅ callback确认发送结果 │
│ │
│ Broker侧: │
│ ✅ replication.factor >= 3 │
│ ✅ min.insync.replicas >= 2 │
│ ✅ flush到磁盘(不过度影响性能) │
│ │
│ 消费者侧: │
│ ✅ enable.auto.commit=false(手动提交偏移量) │
│ ✅ 业务处理成功后再提交 │
│ ✅ 幂等性处理(防止重复消费) │
│ │
└─────────────────────────────────────────────────────────────────────┘
Q2:如何处理消息堆积?
markdown
问题:消费者处理速度慢,导致消息堆积
解决方案:
1. 扩容消费者
- 增加消费者实例(同一Consumer Group)
- 前提: Partition数量足够
2. 消费者优化
- 批量处理代替逐条处理
- 异步处理 + 批量确认
- 优化业务逻辑(减少数据库IO)
3. 临时方案
- Kafka:创建新消费者从尾巴消费(跳过堆积)
- RabbitMQ:惰性队列(存磁盘)
4. 监控预警
- 监控消费延迟
- 设置堆积阈值告警
Q3:Kafka如何实现Exactly-Once?
ini
Kafka Exactly-Once语义实现:
┌─────────────────────────────────────────────────────────────────────┐
│ Kafka事务机制 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ 1. 幂等性Producer(单会话内幂等) │
│ enable.idempotence=true │
│ Producer有唯一PID,消息有唯一序列号 │
│ Broker去重 │
│ │
│ 2. 事务API(跨会话/跨分区) │
│ producer.initTransactions(); │
│ producer.beginTransaction(); │
│ producer.send(record); │
│ producer.sendOffsetsToTransaction(consumerOffsets, consumerGroupId); │
│ producer.commitTransaction(); │
│ │
│ 3. Exactly-Once Source Connector │
│ Kafka Connect支持exactly-once读取外部系统 │
│ │
│ 注意:消费端的exactly-once需要业务配合 │
│ - 使用事务保证消费和偏移量提交的原子性 │
│ │
└─────────────────────────────────────────────────────────────────────┘
七、总结
sql
┌─────────────────────────────────────────────────────────────────────┐
│ 消息队列知识地图 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ Kafka ────────────────────────────────────────────────────────→ │
│ Topic/Partition/Segment → ISR/Leader选举 → 幂等性/事务 │
│ │
│ RabbitMQ ──────────────────────────────────────────────────────→ │
│ Exchange/Queue/Binding → 交换机类型 → 延迟消息 │
│ │
│ 可靠性 ────────────────────────────────────────────────────────→ │
│ 生产者确认 → 持久化 → 消费者确认 → 幂等性 │
│ │
│ 顺序保证 ──────────────────────────────────────────────────────→ │
│ 单Partition → 全局顺序 │
│ │
│ 选型指南 ──────────────────────────────────────────────────────→ │
│ 日志/大数据 → Kafka │
│ 业务消息/RPC → RabbitMQ │
│ │
└─────────────────────────────────────────────────────────────────────┘
🎯 讨论话题
大家在使用消息队列时遇到过哪些"坑"?是怎么解决的?
往期热门文章推荐:
**如果这篇文章对你有帮助,欢迎点赞、收藏、转发!我们下期再见!**👋