一、核心共性(底层逻辑 + 查询原则)
- 查询核心依赖:均需通过中间件的「元数据查询 API」获取路由信息(Kafka 的集群元数据、RocketMQ 的 NameServer 路由、RabbitMQ 的 Broker 节点信息);
- 存储单元与 Broker 关系:查询后可通过「存储单元→Broker」的绑定关系,间接确认目标节点(如 RocketMQ 的 Queue.getBrokerName ()、Kafka 的 PartitionInfo.leader ());
- 查询时机:建议启动时查询一次缓存,后续定时刷新(避免频繁查询元数据影响性能)。
二、分 MQ 完整方案(含查询 + 使用)
(一)Kafka
| 需求类型 | 核心方案 | 实现方式(使用) | 配置复杂度 | 适用场景 |
|---|---|---|---|---|
| 相同标识聚合一队列 | 原生分区器(Key Hash 映射) | 消息 Key 设为业务标识,原生DefaultPartitioner自动 Hash→Partition |
最低(零开发) | 高吞吐、顺序消费、大数据量 |
| 暴力指定 Partition(队列) | 发送时直接指定分区号 | ProducerRecord(topic, 分区号, key, value),跳过分区器 |
低 | 物理隔离、低并发单一存储 |
| 暴力指定 Broker | 间接指定目标 Broker 的 Partition | 先查询目标 Broker 的 Partition,再指定该分区发送 | 低 | 核心业务独立 Broker、物理隔离 |
新增:Java 代码获取 Partition、Broker 信息
java
运行
import org.apache.kafka.clients.admin.AdminClient;
import org.apache.kafka.clients.admin.DescribeTopicsResult;
import org.apache.kafka.clients.admin.TopicDescription;
import org.apache.kafka.clients.producer.KafkaProducer;
import org.apache.kafka.clients.producer.ProducerConfig;
import org.apache.kafka.common.TopicPartitionInfo;
import org.apache.kafka.common.Node;
import java.util.Collections;
import java.util.Properties;
import java.util.concurrent.ExecutionException;
public class KafkaMetadataQuery {
private static final String BOOTSTRAP_SERVERS = "Kafka BrokerIP:9092";
private static final String TOPIC = "topic_order";
// 1. 获取Topic的所有Partition和对应的Broker信息
public static void queryPartitionAndBroker() throws ExecutionException, InterruptedException {
Properties adminProps = new Properties();
adminProps.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, BOOTSTRAP_SERVERS);
AdminClient adminClient = AdminClient.create(adminProps);
// 查询Topic元数据
DescribeTopicsResult describeResult = adminClient.describeTopics(Collections.singleton(TOPIC));
TopicDescription topicDesc = describeResult.values().get(TOPIC).get();
// 遍历所有Partition,获取Partition索引、Leader Broker(主节点)
for (TopicPartitionInfo partitionInfo : topicDesc.partitions()) {
int partition = partitionInfo.partition();
Node leaderBroker = partitionInfo.leader(); // Partition的主Broker
String brokerHost = leaderBroker.host();
int brokerPort = leaderBroker.port();
String brokerId = String.valueOf(leaderBroker.id());
System.out.printf("Partition:%d → Leader Broker:%s(IP:%s:%d)%n",
partition, brokerId, brokerHost, brokerPort);
}
adminClient.close();
}
// 2. 示例:查询后指定目标Broker的Partition发送(暴力指定Broker)
public static void sendToTargetBroker() throws ExecutionException, InterruptedException {
// 第一步:查询获取目标Broker的Partition(假设目标Broker ID为0,对应Partition 2)
int targetPartition = 2; // 从queryPartitionAndBroker()结果中获取
// 第二步:指定该Partition发送
Properties producerProps = new Properties();
producerProps.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, BOOTSTRAP_SERVERS);
producerProps.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringSerializer");
producerProps.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringSerializer");
KafkaProducer<String, String> producer = new KafkaProducer<>(producerProps);
producer.send(new ProducerRecord<>(TOPIC, targetPartition, "KEY", "暴力指定Broker的Partition消息"),
(metadata, e) -> {
if (e == null) {
System.out.printf("发送到Partition:%d,Broker:%s%n",
metadata.partition(), metadata.topicPartition().topic());
}
});
producer.close();
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
queryPartitionAndBroker(); // 查询Partition和Broker映射
sendToTargetBroker(); // 暴力指定Broker发送
}
}
输出结果(查询)
plaintext
Partition:0 → Leader Broker:0(IP:192.168.1.100:9092)
Partition:1 → Leader Broker:1(IP:192.168.1.101:9092)
Partition:2 → Leader Broker:0(IP:192.168.1.100:9092)
(二)RocketMQ
| 需求类型 | 核心方案 | 实现方式(使用) | 配置复杂度 | 适用场景 |
|---|---|---|---|---|
| 相同标识聚合一队列 | 自定义 Hash 队列选择器 | 实现MessageQueueSelector,业务标识 Hash 取模→Queue;发送时传入选择器 |
中等(需写选择器) | 中高吞吐、顺序消费、复杂路由 |
| 暴力指定 Queue(队列) | 固定 Queue 索引选择器 | 自定义选择器直接返回目标 Queue 索引(如 0 号 Queue) | 中等 | 单一队列存储、低并发场景 |
| 暴力指定 Broker | Message.setBrokerName() |
先查询 Broker 名称,发送前msg.setBrokerName("broker-a") |
低 | 核心业务独立 Broker、物理隔离 |
新增:Java 代码获取 Queue、Broker 信息
java
运行
import org.apache.rocketmq.client.MQAdminExt;
import org.apache.rocketmq.client.producer.DefaultMQProducer;
import org.apache.rocketmq.common.MQVersion;
import org.apache.rocketmq.common.protocol.route.BrokerData;
import org.apache.rocketmq.common.protocol.route.MessageQueue;
import org.apache.rocketmq.common.protocol.route.TopicRouteData;
import java.util.List;
public class RocketMQMetadataQuery {
private static final String NAMESRV_ADDR = "NameServerIP:9876";
private static final String TOPIC = "TEST_TOPIC";
// 1. 获取Topic的所有Queue和对应的Broker信息
public static void queryQueueAndBroker() throws Exception {
// 创建MQAdminExt实例(用于查询路由元数据)
MQAdminExt mqAdmin = new DefaultMQProducer("temp-query-group");
mqAdmin.setNamesrvAddr(NAMESRV_ADDR);
mqAdmin.setVipChannelEnabled(false);
try {
// 获取Topic路由数据
TopicRouteData routeData = mqAdmin.examineTopicRouteInfo(TOPIC);
// 遍历Broker信息
List<BrokerData> brokerDataList = routeData.getBrokerDatas();
for (BrokerData brokerData : brokerDataList) {
String brokerName = brokerData.getBrokerName(); // Broker名称
String brokerIp = brokerData.getBrokerAddrs().values().iterator().next(); // Broker IP:端口
// 遍历该Broker下的所有Queue
List<MessageQueue> queues = routeData.getMessageQueues();
for (MessageQueue mq : queues) {
if (brokerName.equals(mq.getBrokerName())) {
int queueId = mq.getQueueId();
System.out.printf("Broker:%s(IP:%s)→ Queue:%d%n",
brokerName, brokerIp, queueId);
}
}
}
} finally {
mqAdmin.shutdown();
}
}
// 2. 示例:查询后暴力指定Queue发送
public static void sendToFixedQueue() throws Exception {
// 第一步:从queryQueueAndBroker()结果中获取目标Queue(如broker-a的0号Queue)
String targetBrokerName = "broker-a";
int targetQueueId = 0;
// 第二步:自定义固定Queue选择器
DefaultMQProducer producer = new DefaultMQProducer("fixed-queue-producer");
producer.setNamesrvAddr(NAMESRV_ADDR);
producer.start();
for (int i = 0; i < 3; i++) {
Message msg = new Message(TOPIC, "TAG_FIXED", ("指定Queue消息:" + i).getBytes());
// 传入自定义选择器,指定Queue
producer.send(msg, (mqs, msg1, arg) -> {
for (MessageQueue mq : mqs) {
if (targetBrokerName.equals(mq.getBrokerName()) && targetQueueId == mq.getQueueId()) {
return mq;
}
}
throw new IllegalArgumentException("目标Queue不存在");
}, null);
}
producer.shutdown();
System.out.println("已发送到指定Queue:" + targetQueueId + "(Broker:" + targetBrokerName + ")");
}
public static void main(String[] args) throws Exception {
queryQueueAndBroker(); // 查询Queue和Broker映射
sendToFixedQueue(); // 暴力指定Queue发送
}
}
输出结果(查询)
plaintext
Broker:broker-a(IP:192.168.1.102:10911)→ Queue:0
Broker:broker-a(IP:192.168.1.102:10911)→ Queue:1
Broker:broker-b(IP:192.168.1.103:10911)→ Queue:0
(三)RabbitMQ
| 需求类型 | 核心方案 | 实现方式(使用) | 配置复杂度 | 适用场景 |
|---|---|---|---|---|
| 相同标识聚合一队列 | DirectExchange+Hash 路由 Key | 多 Queue 绑定不同 Routing Key;业务标识 Hash→生成对应 Routing Key | 最高(需配置 Exchange + 绑定) | 多维度分组、复杂路由、低延迟 |
| 暴力指定 Queue(队列) | DirectExchange + 单一 Queue 绑定 | 单一 Queue 绑定固定 Routing Key;所有消息用该 Key 发送 | 中 | 单一队列存储、简单聚合需求 |
| 暴力指定 Broker | Queue 部署指定 Broker(间接指定) | 创建 Queue 时通过x-queue-master-node指定 Broker;发送到该 Queue |
中 | 核心业务独立 Broker、物理隔离 |
新增:Java 代码获取 Queue、Broker 信息
java
运行
import org.springframework.amqp.rabbit.core.RabbitAdmin;
import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import com.rabbitmq.client.Channel;
import java.util.List;
import java.util.Map;
public class RabbitMQMetadataQuery {
private static final String RABBITMQ_HOST = "RabbitMQ IP";
private static final int RABBITMQ_PORT = 5672;
private static final String RABBITMQ_USER = "username";
private static final String RABBITMQ_PASS = "password";
private static final String VIRTUAL_HOST = "/";
// 1. 获取Queue的部署Broker信息(需RabbitMQ客户端API)
public static void queryQueueAndBroker() throws Exception {
// 创建连接工厂
ConnectionFactory factory = new CachingConnectionFactory(RABBITMQ_HOST, RABBITMQ_PORT);
factory.setUsername(RABBITMQ_USER);
factory.setPassword(RABBITMQ_PASS);
factory.setVirtualHost(VIRTUAL_HOST);
// 创建RabbitAdmin(用于查询元数据)
RabbitAdmin rabbitAdmin = new RabbitAdmin(factory);
Channel channel = rabbitAdmin.getRabbitTemplate().getConnectionFactory().createConnection().createChannel(false);
try {
// 目标Queue名称
String queueName = "queue.fixed.target";
// 查询Queue详情(包含部署的Broker信息)
Map<String, Object> queueDeclare = channel.queueDeclarePassive(queueName);
String brokerNode = (String) queueDeclare.get("x-queue-master-node"); // Queue部署的Broker节点
// 查询所有Broker节点信息
List<Map<String, Object>> brokers = rabbitAdmin.getClusterNodes();
for (Map<String, Object> broker : brokers) {
String nodeName = (String) broker.get("name");
String nodeType = (String) broker.get("type"); // disc(磁盘节点)/ram(内存节点)
if (nodeName.equals(brokerNode)) {
System.out.printf("Queue:%s → 部署Broker:%s(类型:%s)%n",
queueName, nodeName, nodeType);
}
}
} finally {
channel.close();
factory.destroy();
}
}
// 2. 示例:查询后暴力指定Queue发送(间接指定Broker)
public static void sendToFixedQueue() {
ConnectionFactory factory = new CachingConnectionFactory(RABBITMQ_HOST, RABBITMQ_PORT);
factory.setUsername(RABBITMQ_USER);
factory.setPassword(RABBITMQ_PASS);
factory.setVirtualHost(VIRTUAL_HOST);
RabbitAdmin rabbitAdmin = new RabbitAdmin(factory);
// 发送到查询确认的目标Queue
rabbitAdmin.getRabbitTemplate().convertAndSend(
"exchange.fixed", // 绑定的Exchange
"FIXED_ROUTING_KEY", // 绑定的Routing Key
"暴力指定Queue(Broker)消息"
);
System.out.println("已发送到指定Queue,Broker由Queue部署决定");
factory.destroy();
}
public static void main(String[] args) throws Exception {
queryQueueAndBroker(); // 查询Queue和Broker映射
sendToFixedQueue(); // 暴力指定Queue发送
}
}
输出结果(查询)
plaintext
Queue:queue.fixed.target → 部署Broker:rabbit@broker-a(类型:disc)
三、核心差异汇总表(含查询能力)
| 特性 | Kafka | RocketMQ | RabbitMQ |
|---|---|---|---|
| 相同标识聚合核心依赖 | 消息 Key + 原生分区器 | 自定义 MessageQueueSelector | DirectExchange + Hash 路由 Key + 绑定 |
| 暴力指定 Queue 方式 | 直接指定分区号 | 固定 Queue 索引选择器 | 单一 Queue + 固定 Routing Key 绑定 |
| 暴力指定 Broker 方式 | 间接指定目标 Broker 的 Partition | 直接调用 setBrokerName () | Queue 部署时指定 Broker 节点 |
| 获取元数据 API | AdminClient + DescribeTopicsResult | MQAdminExt + TopicRouteData | RabbitAdmin + Channel.queueDeclarePassive() |
| 配置复杂度排序 | 最低(原生支持多) | 中等(需少量自定义) | 最高(依赖 Exchange + 绑定) |
| 高吞吐适配性 | 最优 | 良好 | 一般 |
| 复杂路由适配性 | 一般 | 良好 | 最优 |
四、关键注意事项(避坑)
- 元数据查询权限:Kafka 的 AdminClient、RocketMQ 的 MQAdminExt、RabbitMQ 的 RabbitAdmin 均需对应权限(如 Kafka 的 DescribeTopics 权限、RabbitMQ 的 monitor 权限),否则查询失败;
- 存储单元数量影响:增减 Partition/Queue 后,需重新查询元数据并更新代码中的目标标识(如分区号、Queue 索引),否则可能路由失败;
- 容灾处理:暴力指定后目标节点宕机时,Kafka 依赖副本自动切换,RocketMQ/RabbitMQ 需在代码中添加重试逻辑(如查询备选 Broker 的 Partition/Queue);
- RabbitMQ 特殊注意 :
x-queue-master-node参数仅在 RabbitMQ 集群模式生效,单机模式下 Queue 默认部署在唯一 Broker,无需额外配置。
五、选型建议
- 追求「零开发、高吞吐 + 简单查询」:选 Kafka(AdminClient 查询便捷,原生支持聚合和指定分区);
- 已用 RocketMQ 生态:优先用
setBrokerName()暴力指定 Broker(代码简洁),聚合用自定义选择器,查询用 MQAdminExt(路由元数据直观); - 需「多维度分组、复杂路由」:选 RabbitMQ(Exchange 支持灵活绑定),查询需结合 Channel 和 RabbitAdmin,配置稍复杂但扩展性强。