Java-218 RocketMQ Java API 实战:同步/异步 Producer 与 Pull/Push Consumer

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案例 详解
🔗 大数据模块直达链接

相关推荐
.卡2 小时前
(022)FastJson 序列化导致的 java.util.ConcurrentModificationException
java
独自破碎E2 小时前
Kafka的索引设计有什么亮点?
数据库·分布式·kafka
不爱吃米饭_2 小时前
Spring Security、Apache Shiro、Sa-Token,主流安全框架如何选择?
java·安全
我命由我123452 小时前
Java 开发 - 含有 null 值字段的对象排序(自定义 Comparator、使用 Comparator、使用 Stream API)
java·开发语言·学习·java-ee·intellij-idea·学习方法·intellij idea
GISERLiu2 小时前
Spring Boot + Spring Security
java·spring boot·spring
独自破碎E2 小时前
Kafka中关于事务消息的实现
分布式·kafka
ppo_wu2 小时前
Kafka 3.9.0:部署、监控与消息发送教程
java·linux·spring boot·分布式·后端·spring·kafka
阿干tkl2 小时前
Tomcat文件上传及下载
java·tomcat
艾莉丝努力练剑2 小时前
艾莉丝努力练剑的2025年度总结
java·大数据·linux·开发语言·c++·人工智能·python