TL;DR
- 场景:本地或 Docker 学习 RocketMQ Java API,覆盖 Producer/Consumer 四种写法
- 结论:Producer 关注 namesrvAddr/编码/发送语义;Consumer 关注 group/offset/重试与线程生命周期
- 产出:可直接运行的同步/异步生产者 + Pull/Poll 消费者 + Push 并发监听示例

RocketMQ API学习
WzkProducer
java
package icu.wzk;
public class WzkProducer {
public static void main(String[] args) throws Exception {
// 1) Producer Group:同一类生产者的逻辑分组
// 作用:事务消息、故障容错、运维统计等会依赖 group 概念(不是 Topic)
DefaultMQProducer producer = new DefaultMQProducer("wzk-icu");
// 2) NameServer 地址:客户端先问 NameServer 拿到 Topic -> Broker 路由信息,再直连 Broker 发消息
// 注意:这里不要用 0.0.0.0(表示"本机监听所有网卡",不是"可被访问的地址")
// - 本机跑:用 localhost:9876 或 127.0.0.1:9876
// - Docker 场景:用宿主机 IP:9876,或按你 compose 配的 host.docker.internal:9876
producer.setNamesrvAddr("localhost:9876");
// 3) 启动 producer:会初始化网络连接、线程池、与 namesrv/broker 的通信等
producer.start();
try {
// 4) 构造消息
// Topic:路由与隔离的最主要维度(必填)
// Tag:可选的二级分类,用于消费者侧过滤(轻量过滤,不是强隔离)
// Body:字节数组;建议显式指定编码,避免平台默认编码差异
String topic = "wzk-topic";
String tag = "wzk-tag";
String body = "Hello RocketMQ!";
Message message = new Message(
topic,
tag,
body.getBytes(StandardCharsets.UTF_8)
);
// 5) 同步发送:阻塞等待 broker 返回结果
// 成功会返回 SendResult(包含 msgId、queueId、offset 等)
SendResult sendResult = producer.send(message);
// 6) 打印结果:学习阶段直接看就行
System.out.println(sendResult);
} finally {
// 7) 关闭 producer:释放资源(网络连接、线程池)
// 放在 finally 防止异常导致进程不退出或资源泄露
producer.shutdown();
}
}
}
测试运行代码如下所示:

WzkAsyncProducer
java
package icu.wzk;
public class WzkAsyncProducer {
public static void main(String[] args) throws Exception {
// 1) Producer Group:同一类生产者的逻辑分组(事务/运维/容错会用到)
DefaultMQProducer producer = new DefaultMQProducer("wzk-icu");
// 2) NameServer:不要写 0.0.0.0(那是"监听所有网卡"的占位符,不是"可访问地址")
// 本机:localhost:9876
// Docker:通常是宿主机IP:9876 或 host.docker.internal:9876(看你的 broker.conf/brokerIP1)
producer.setNamesrvAddr("127.0.0.1:9876");
// 3) 启动 producer:初始化连接/线程池/路由信息
producer.start();
final int total = 100;
CountDownLatch latch = new CountDownLatch(total);
try {
for (int i = 0; i < total; i++) {
String topic = "wzk-topic";
String tag = "wzk-tag";
String body = "Hello RocketMQ " + i;
Message msg = new Message(
topic,
tag,
body.getBytes(StandardCharsets.UTF_8)
);
// 4) 异步发送:立即返回;结果通过回调线程通知
// 回调里只做轻量操作(打印/计数);不要做阻塞IO/重计算
producer.send(msg, new SendCallback() {
@Override
public void onSuccess(SendResult sendResult) {
System.out.println("发送成功 " + sendResult);
latch.countDown();
}
@Override
public void onException(Throwable throwable) {
// 失败常见原因:路由未就绪、broker不可达、超时、消息过大等
System.out.println("发送失败 " + throwable.getMessage());
throwable.printStackTrace();
latch.countDown();
}
});
}
// 5) 等待所有回调结束(替代 Thread.sleep)
// 超时后继续 shutdown:学习测试可以;生产要做失败统计/重试/兜底
boolean finished = latch.await(10, TimeUnit.SECONDS);
if (!finished) {
System.out.println("等待回调超时:仍有部分消息未返回 sendResult");
}
} finally {
// 6) 关闭 producer:释放连接与线程池
producer.shutdown();
}
}
}
测试运行代码如下所示:

WzkPullConsumer
java
package icu.wzk;
public class WzkPullConsumer {
public static void main(String[] args) throws Exception {
// 1) Consumer Group:同一类消费者的逻辑分组
// - 广播/集群、负载均衡、消费进度(offset)等都跟 group 强相关
DefaultLitePullConsumer consumer = new DefaultLitePullConsumer("wzk-icu");
// 2) NameServer:不要写 0.0.0.0(那是"监听地址",不是"可访问地址")
// 本机:localhost:9876
// Docker:通常是宿主机IP:9876 或 host.docker.internal:9876(取决于 broker.conf 的 brokerIP1 配置)
consumer.setNamesrvAddr("localhost:9876");
// 3) 订阅:topic + tag expression
// "*" 表示接收该 topic 下所有 tag
consumer.subscribe("wzk-topic", "*");
// 4) 自动提交 offset:
// true => poll 返回后由客户端自动提交消费进度(更省事,但业务失败时不易回滚)
// false => 你需要在业务处理成功后手动 commitSync/commit(更可控)
consumer.setAutoCommit(true);
// 5) 启动 consumer:初始化与 namesrv/broker 的连接、拉取线程等
consumer.start();
try {
while (true) {
// 6) poll 拉取:拉取一批消息;没消息会阻塞到超时
// 老版本有 poll(long timeoutMillis),新版本也支持 poll(Duration)
List<MessageExt> msgs = consumer.poll(3000);
// 7) 业务处理:这里只做打印;生产场景要加幂等/失败重试策略
for (MessageExt msg : msgs) {
String body = new String(msg.getBody(), StandardCharsets.UTF_8);
System.out.printf(
"msgId=%s, queueId=%d, reconsumeTimes=%d, body=%s%n",
msg.getMsgId(),
msg.getQueueId(),
msg.getReconsumeTimes(),
body
);
}
}
} finally {
// 8) 关闭 consumer:释放连接和线程池
consumer.shutdown();
}
}
}
测试运行代码如下所示:(类名改了一下方便区分)

WzkPushConsumer
java
package icu.wzk;
public class WzkPushConsumer {
public static void main(String[] args) throws Exception {
// 1) Consumer Group:同一业务语义的消费者分组
// 同一个 group 内:消息会在多个实例之间做负载均衡(集群消费模式下)
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("wzk-icu");
// 2) NameServer 地址:用于获取 Topic -> Broker 的路由信息
// 本机测试:localhost:9876
// Docker:按你的 compose 配置可能需要改成宿主机 IP / host.docker.internal
consumer.setNamesrvAddr("localhost:9876");
// 3) 订阅 Topic + Tag 过滤
// - topic 必须存在于 broker 路由中(学习环境可 autoCreateTopicEnable=true 自动创建)
// - tag 表达式:
// "*" 表示订阅该 topic 下所有 tag
// "tagA || tagB" 表示只消费指定 tag
consumer.subscribe("wzk-topic", "*");
// 4) 注册并发消费监听器(PushConsumer 实际是 client 侧拉取 + 回调)
// 回调语义:
// - 返回 CONSUME_SUCCESS:本批消息消费成功,broker 会推进 offset
// - 返回 RECONSUME_LATER:本批消息稍后重试(会增加重试次数,可能进入死信队列)
consumer.setMessageListener((MessageListenerConcurrently) (msgs, context) -> {
// 4.1) 当前消息来自哪个队列(同一个 topic 下有多个 MessageQueue)
// 并发消费时:同一队列内尽量保持顺序,但整体仍是并发批量回调
MessageQueue mq = context.getMessageQueue();
System.out.println("MQ = " + mq);
for (MessageExt msg : msgs) {
try {
// 4.2) 常用元信息:topic、tags、keys、msgId、重试次数等
// - keys 常用于业务唯一键/去重/索引
// - reconsumeTimes 表示已重试次数
String body = new String(msg.getBody(), StandardCharsets.UTF_8);
System.out.printf(
"msgId=%s topic=%s tags=%s keys=%s reconsumeTimes=%d body=%s%n",
msg.getMsgId(),
msg.getTopic(),
msg.getTags(),
msg.getKeys(),
msg.getReconsumeTimes(),
body
);
} catch (Exception e) {
// 4.3) 这里如果直接返回 SUCCESS,会把消息当成已消费,导致"丢消息"
// 学习阶段建议返回 RECONSUME_LATER 触发重试,方便观察机制
e.printStackTrace();
return ConsumeConcurrentlyStatus.RECONSUME_LATER;
}
}
// 4.4) 本批消息全部处理成功
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
});
// 5) 启动 consumer:开始拉取消息并回调 listener
consumer.start();
// 6) 主线程不退出(示例程序需要阻塞住)
// 否则 main 结束,JVM 退出,consumer 线程也会被停掉
System.out.println("WzkPushConsumer started.");
Thread.currentThread().join();
}
}
测试运行代码如下所示:

错误速查
| 症状 | 根因定位 | 修复 |
|---|---|---|
| Producer/Consumer 连不上 NameServer | namesrvAddr 写成 0.0.0.0(监听地址,不是可访问地址)或端口不通 | 启动日志 + telnet/nc 到 9876;看是否打印路由拉取失败 本机用 localhost/127.0.0.1:9876;Docker 用宿主机 IP 或 host.docker.internal(与 brokerIP1/端口映射一致) |
| 发送偶发失败/超时(异步更明显) | 路由未就绪、broker 不可达、网络抖动、回调阻塞导致堆积 | onException 打印异常;观察是否集中在启动初期;看 client 日志 启动后先等待路由稳定;回调只做轻量逻辑;必要时做重试与失败统计 |
| 消费端"看起来跑着但没消息" | 订阅的 topic/tag 不匹配;topic 未创建/路由未刷新;消费位点已在末尾 | 检查 subscribe 表达式;看 broker/consumer 日志;看是否能打印到队列信息 学习期用 subscribe(topic,"*");确认 producer topic 一致;必要时重置消费位点或换新 group |
| PushConsumer 业务异常但消息不再重试 | 捕获异常后仍返回 CONSUME_SUCCESS(推进 offset,相当于确认成功) | 看 listener 返回值;观察 reconsumeTimes 是否增长 异常时返回 RECONSUME_LATER;只在业务成功后返回 CONSUME_SUCCESS |
| PullConsumer 重启后重复或丢处理 | AutoCommit=true,poll 返回即自动提交 offset,业务失败无法回滚 | 观察是否开启 autoCommit;对比业务失败时 offset 行为 需要可控语义:setAutoCommit(false),业务成功后手动 commitSync/commit,并实现幂等 |
| 程序一闪而过/Consumer 很快退出 | main 线程结束导致 JVM 退出,consumer 线程被停掉 | 观察进程生命周期与日志 PushConsumer 用 join/阻塞;PullConsumer 用 while(true) 已阻塞,确保 finally 不提前触发 |
| 中文/特殊字符乱码 | body 编码未显式指定,平台默认编码不一致 | 对比发送端 getBytes 与消费端 new String 发送端/消费端统一 StandardCharsets.UTF_8 |
其他系列
🚀 AI篇持续更新中(长期更新)
AI炼丹日志-29 - 字节跳动 DeerFlow 深度研究框斜体样式架 私有部署 测试上手 架构研究 ,持续打造实用AI工具指南!
AI研究-132 Java 生态前沿 2025:Spring、Quarkus、GraalVM、CRaC 与云原生落地
🔗 AI模块直达链接
💻 Java篇持续更新中(长期更新)
Java-216 RocketMQ 4.5.1 在 JDK9+ 从0到1全流程启动踩坑全解:脚本兼容修复(GC 参数/CLASSPATH/ext.dirs)
MyBatis 已完结,Spring 已完结,Nginx已完结,Tomcat已完结,分布式服务已完结,Dubbo已完结,MySQL已完结,MongoDB已完结,Neo4j已完结,FastDFS 已完结,OSS已完结,GuavaCache已完结,EVCache已完结,RabbitMQ已完结,RocketMQ正在更新... 深入浅出助你打牢基础!
🔗 Java模块直达链接
📊 大数据板块已完成多项干货更新(300篇):
包括 Hadoop、Hive、Kafka、Flink、ClickHouse、Elasticsearch 等二十余项核心组件,覆盖离线+实时数仓全栈!
大数据-278 Spark MLib - 基础介绍 机器学习算法 梯度提升树 GBDT案例 详解
🔗 大数据模块直达链接