在微服务架构中,服务之间的通信至关重要。而消息队列 (Message Queue, MQ) 作为一种异步通 信机制,能够有效解耦服务,提高系统的可扩展性、可靠性和最终一致性。
在微服务架构中,服务之间通常通过同步调用 (如 REST API) 进行通信。然而,同步调用存在以下问题:
耦合度高: 服务之间直接依赖,任何一个服务出现故障都会影响其他服务。
性能瓶颈: 同步调用会阻塞线程,当调用链路过长时,会导致系统性能下降。
可扩展性差: 增加新的服务需要修改现有服务的代码,扩展性较差。
消息队列通过异步通信机制,能够有效解决上述问题:
解耦服务: 服务之间通过消息队列进行通信,无需直接依赖,降低了耦合度。
提高性能: 异步通信不会阻塞线程,提高了系统的吞吐量和响应速度。
增强可扩展性: 新的服务可以订阅感兴趣的消息,无需修改现有服务的代码,易于扩展。
特性 | RocketMQ | Kafka | RabbitMQ |
---|---|---|---|
架构设计 | 分布式架构,NameServer + Broker + Producer + Consumer | 分布式架构,Zookeeper + Broker + Producer + Consumer | 基于 AMQP 协议,Broker + Exchange + Queue + Producer + Consumer |
设计目标 | 高吞吐、低延迟、高可靠性,支持事务消息、顺序消息等 | 超高吞吐、低延迟,适合日志收集、流处理等大数据场景 | 灵活的路由、消息确认机制,适合企业级应用和复杂消息路由场景 |
性能 | 单机吞吐量 10 万级 TPS,延迟毫秒级 | 单机吞吐量百万级 TPS,延迟毫秒级 | 单机吞吐量万级 TPS,延迟较低 |
可靠性 | 支持消息持久化、消息重试、消息轨迹,保证消息不丢失 | 支持消息持久化、多副本机制,保证消息不丢失 | 支持消息持久化、ACK 机制,保证消息不丢失 |
功能特性 | 支持顺序消息、事务消息、延迟消息、消息过滤、消息轨迹等 | 支持分区、副本、流处理,功能相对单一 | 支持灵活的路由规则、消息确认、优先级队列、死信队列等 |
消息模型 | 基于 Topic 的发布订阅模型,支持 Pull 和 Push 模式 | 基于 Topic 的分区模型,支持 Pull 模式 | 基于 Exchange 和 Queue 的路由模型,支持 Push 模式 |
消息顺序 | 支持严格的消息顺序(分区顺序) | 支持分区内的消息顺序 | 不支持全局消息顺序,单队列内有序 |
事务支持 | 支持分布式事务消息(半消息机制) | 不支持事务消息,但可以通过流处理实现类似功能 | 支持事务消息,但性能较差 |
延迟消息 | 支持延迟消息(固定级别) | 不支持延迟消息,可通过外部实现 | 支持延迟消息(插件实现) |
消息堆积能力 | 支持海量消息堆积,性能稳定 | 支持海量消息堆积,性能稳定 | 消息堆积能力较弱,性能下降明显 |
扩展性 | 支持水平扩展,NameServer 无状态,Broker 可动态扩展 | 支持水平扩展,Zookeeper 管理集群状态,Broker 可动态扩展 | 支持集群扩展,但扩展性相对较弱 |
运维复杂度 | 中等,需要部署 NameServer 和 Broker,配置较复杂 | 较高,需要部署 Zookeeper 和 Broker,运维成本较高 | 较低,部署简单,易于管理 |
社区生态 | 阿里巴巴开源,中文社区活跃,文档丰富 | Apache 顶级项目,全球社区活跃,生态丰富 | 开源,社区活跃,文档丰富,插件生态完善 |
适用场景 | 电商、金融、物联网等高并发、高可靠性场景 | 日志收集、实时流处理、大数据分析等超高吞吐场景 | 企业级应用、复杂消息路由、低延迟场景 |
优点 | 高性能、高可靠、功能丰富,适合大规模分布式系统 | 超高吞吐、低延迟,适合大数据场景 | 灵活的路由、消息确认机制,易于使用 |
缺点 | 学习成本较高,配置复杂 | 功能相对单一,运维成本高 | 性能和吞吐量较低,不适合超大规模场景 |
RabbitMQ 在吞吐量方面虽然稍逊于 Kafka 和 RocketMQ ,但是由于它基于 erlang 开发,所以并发能力很强,性能极其好,延时很低,达到微秒级。如果业务场景对并发量要求不是太高(十万级、百万级),那这几个消息队列中,RabbitMQ 一定是你的首选。如果是大数据领域的实时计算、日志采集等场景,用 Kafka 是业内标准的.
RocketMQ 阿里出品,Java 系开源项目,源代码我们可以直接阅读,然后可以定制自己公司的MQ,并且RocketMQ 有阿里巴巴的实际业务场景的实战考验。RocketMQ 社区活跃度相对较为一般,不过也还可以,文档相对来说简单一些.
kafka 的特点其实很明显,就是仅仅提供较少的核心功能,但是提供超高的吞吐量,ms 级的延迟,极高的可用性以及可靠性,而且分布式可以任意扩展。同时 kafka 最好是支撑较少的 topic 数量即可,保证其超高吞吐量。kafka 唯一的一点劣势是有可能消息重复消费,那么对数据准确性会造成极其轻微的影响,在大数据领域中以及日志采集中,这点轻微影响可以忽略这个特性天然适合大数据实时计算以及日志收集
JDK 1.8+
Maven(
Linux/Unix 环境(推荐)或 Windows(开发测试)
# 下载 RocketMQ
wget https://archive.apache.org/dist/rocketmq/4.9.4/rocketmq-all-4.9.4-bin-release.zip
unzip rocketmq-all-4.9.4-bin-release.zip
cd rocketmq-4.9.4
3.1.3. 启动 NameServer
# 启动 NameServer
nohup sh bin/mqnamesrv &
# 查看日志
tail -f ~/logs/rocketmqlogs/namesrv.log
# 修改 Broker 配置(可选)
vi conf/broker.conf
# 添加配置(例如设置 NameServer 地址)
namesrvAddr=localhost:9876
# 启动 Broker
nohup sh bin/mqbroker -n localhost:9876 &
# 查看日志
tail -f ~/logs/rocketmqlogs/broker.log
# 查看 Broker 状态
sh bin/mqadmin clusterList -n localhost:9876
多 NameServer:部署多个 NameServer 实例,Broker 配置所有 NameServer 地址。
Broker 主从:通过配置文件设置 brokerRole
(SYNC_MASTER/ SLAVE)和 brokerId
(0 表示 Master,非 0 表示 Slave)。
org.apache.rocketmq
rocketmq-client
4.9.4
public class ProducerDemo {
public static void main(String[] args) throws Exception {
// 1. 创建生产者实例
DefaultMQProducer producer = new DefaultMQProducer("producer_group");
// 2. 设置 NameServer 地址
producer.setNamesrvAddr("localhost:9876");
// 3. 启动生产者
producer.start();
// 4. 创建消息
Message msg = new Message("test_topic", "tagA", "Hello RocketMQ".getBytes());
// 5. 发送消息(同步方式)
SendResult result = producer.send(msg);
System.out.println("消息发送结果:" + result);
// 6. 关闭生产者
producer.shutdown();
}
}
public class ConsumerDemo {
public static void main(String[] args) throws Exception {
// 1. 创建消费者实例
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("consumer_group");
// 2. 设置 NameServer 地址
consumer.setNamesrvAddr("localhost:9876");
// 3. 订阅 Topic 和 Tag(* 表示所有 Tag)
consumer.subscribe("test_topic", "*");
// 4. 注册消息监听器
consumer.registerMessageListener((MessageListenerConcurrently) (msgs, context) -> {
for (MessageExt msg : msgs) {
System.out.println("收到消息: " + new String(msg.getBody()));
}
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
});
// 5. 启动消费者
consumer.start();
System.out.println("消费者已启动");
}
}
生产者:
setSendMsgTimeout(int timeout)
:发送超时时间。
setRetryTimesWhenSendFailed(int retryTimes)
:失败重试次数。
消费者:
setConsumeThreadMin(int min)
:最小消费线程数。
setConsumeFromWhere(ConsumeFromWhere consumeFromWhere)
:消费起始位置(如 CONSUME_FROM_LAST_OFFSET
)。
Java:Kafka 需要 Java 环境,建议使用 JDK 8 或更高版本。
Zookeeper:Kafka 依赖 Zookeeper 进行集群管理。
从 Apache Kafka 官网 下载最新版本的 Kafka。
wget https://downloads.apache.org/kafka/3.6.0/kafka_2.13-3.6.0.tgz
tar -xzf kafka_2.13-3.6.0.tgz
cd kafka_2.13-3.6.0
Kafka 依赖 Zookeeper,首先需要启动 Zookeeper。
bin/zookeeper-server-start.sh config/zookeeper.properties
启动 Kafka Broker。
bin/kafka-server-start.sh config/server.properties
创建一个名为 test-topic
的 Topic。
bin/kafka-topics.sh --create --topic test-topic --bootstrap-server localhost:9092 --partitions 1 --replication-factor 1
启动一个生产者并向 test-topic
发送消息。
bin/kafka-console-producer.sh --topic test-topic --bootstrap-server localhost:9092
启动一个消费者并消费 test-topic
中的消息。
ka-console-consumer.sh --topic test-topic --bootstrap-server localhost:9092 --from-beginning
在 Maven 项目中添加 Kafka 客户端依赖。
org.apache.kafka
kafka-clients
3.6.0
以下是一个简单的 Kafka 生产者示例。
import org.apache.kafka.clients.producer.*;
import java.util.Properties;
public class KafkaProducerExample {
public static void main(String[] args) {
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");
Producer producer = new KafkaProducer<>(props);
for (int i = 0; i < 10; i++) {
ProducerRecord record = new ProducerRecord<>("test-topic", "key-" + i, "value-" + i);
producer.send(record, (metadata, exception) -> {
if (exception == null) {
System.out.println("Message sent to topic " + metadata.topic() + " partition " + metadata.partition() + " offset " + metadata.offset());
} else {
exception.printStackTrace();
}
});
}
producer.close();
}
}
以下是一个简单的 Kafka 消费者示例。
import org.apache.kafka.clients.consumer.*;
import org.apache.kafka.common.TopicPartition;
import java.time.Duration;
import java.util.Collections;
import java.util.Properties;
public class KafkaConsumerExample {
public static void main(String[] args) {
Properties props = new Properties();
props.put("bootstrap.servers", "localhost:9092");
props.put("group.id", "test-group");
props.put("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
props.put("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
Consumer consumer = new KafkaConsumer<>(props);
consumer.subscribe(Collections.singletonList("test-topic"));
while (true) {
ConsumerRecords records = consumer.poll(Duration.ofMillis(100));
for (ConsumerRecord record : records) {
System.out.printf("Consumed message with key %s and value %s%n", record.key(), record.value());
}
}
}
}
如果消息出现堆积可能有三种情况:
1.消息的生产者生产消息过快。
2.broker发生了阻塞。
3.消息的消费者消费消息过慢。
可以设置过期时间,不设置不会过期.过期数据根据策略进行删除,不在被消费
解决:
首先要找到消息堆积的原因,是producer太多,还是consumer太少,还是消息消费速度异常,正常的话,可以上线更多的consumer临时解决消息堆积问题。
queue是典型的FIFO,天然顺序,多个queue同时消费时无法绝对保证消息的有序性,所以同一个topic,同一个queue 发消息的时候,一个线程去发送消息,消费的时候一个线程去消费一个queue里的消息。