用Java API拆解RocketMQ的三种Consumer:从简单到复杂,聊聊业务实战

用Java API拆解RocketMQ的三种Consumer:从简单到复杂,聊聊业务实战

大家好,今天咱们来聊聊RocketMQ里三种Consumer的玩法:SimpleConsumer、PushConsumer和PullConsumer。这三兄弟各有各的脾气,咱们就拿Java API做例子,从最朴素的思路开始,一步步看看它们在业务里怎么用,遇到啥问题,又是怎么进化到今天这样靠谱的方案的。顺便,咱们还会聊聊优化方向,咋把那些坑填平,跟主流方案接轨。

先从最朴素的开始:PullConsumer

咱们先说PullConsumer,这家伙是最直白的。简单来说,它就是"你要啥我给你拉啥",主动权全在你手里。代码大概长这样:

java 复制代码
import org.apache.rocketmq.client.consumer.DefaultMQPullConsumer;
import org.apache.rocketmq.common.message.MessageQueue;

public class PullConsumerDemo {
    public static void main(String[] args) throws Exception {
        DefaultMQPullConsumer consumer = new DefaultMQPullConsumer("pull_consumer_group");
        consumer.setNamesrvAddr("localhost:9876");
        consumer.start();

        // 主动拉取消息
        MessageQueue mq = new MessageQueue("TopicTest", "Broker-a", 0);
        long offset = consumer.fetchConsumeOffset(mq, true);
        consumer.pull(mq, "*", offset, 32, pullResult -> {
            System.out.println("拉到消息啦:" + pullResult.getPullStatus());
            pullResult.getMessageExts().forEach(msg -> {
                System.out.println(new String(msg.getBody()));
            });
        });

        consumer.shutdown();
    }
}

业务场景:假设你是个电商系统,要处理订单状态的查询日志。订单量不算大,一天也就5000单,日志量有限,你完全可以定时去拉取消息,自己控制节奏。

朴素带来的问题

  • 麻烦:你得自己管offset(消息消费位置),每次拉完还得记下来,不然重启就懵了,得从头开始或者丢消息。5000单还好,50万单你试试,手动算offset能累死人。
  • 效率低:拉取频率你得自己调,太快浪费资源,太慢消息堆积。假设每秒拉10次,每次拉32条消息,可能有空跑的情况,白浪费性能。
  • 实时性差:定时拉取天然有延迟,订单状态更新慢了,用户体验就受影响。

优化方向:能不能让Broker主动推消息给我?我只管消费,不操心拉取和offset管理?这不就逼近PushConsumer的思路了嘛。

中间派:PushConsumer

PushConsumer就省心多了,Broker直接把消息推过来,你只管接住处理就行。看看代码:

java 复制代码
import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer;
import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus;

public class PushConsumerDemo {
    public static void main(String[] args) throws Exception {
        DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("push_consumer_group");
        consumer.setNamesrvAddr("localhost:9876");
        consumer.subscribe("TopicTest", "*");
        consumer.registerMessageListener((MessageListenerConcurrently) (msgs, context) -> {
            msgs.forEach(msg -> System.out.println(new String(msg.getBody())));
            return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
        });
        consumer.start();
    }
}

业务场景:还是电商,这次是支付成功的通知。每天10万单,每单支付后得马上通知库存系统扣减库存。PushConsumer很适合,因为消息一来就处理,实时性强。

问题暴露

  • 被动性强:消息推过来你得立刻处理,处理慢了怎么办?比如库存系统接口挂了,消息堆积在内存里,10万单可能就撑爆了。默认缓存1000条消息,超了就阻塞。
  • 同步限制:处理逻辑得是同步的,不能异步扔给别的线程。比如你想把库存扣减扔到线程池异步搞定,PushConsumer不支持,你得老老实实等着。
  • 超时尴尬:默认超时1分钟,处理超过这个时间,Broker以为你没消费成功,又重发,重复消费就来了。10万单里哪怕1%超时,1000条重复消息够你喝一壶。

优化方向:能不能让我灵活点?比如控制消息的处理时间,或者异步处理,还得保证不重复消费。这就指向了SimpleConsumer。

进化版:SimpleConsumer

SimpleConsumer是RocketMQ 5.0后推出的新宠,结合了Pull和Push的优点,灵活又好用。代码瞅一眼:

java 复制代码
import org.apache.rocketmq.client.apis.ClientServiceProvider;
import org.apache.rocketmq.client.apis.consumer.SimpleConsumer;

public class SimpleConsumerDemo {
    public static void main(String[] args) throws Exception {
        ClientServiceProvider provider = ClientServiceProvider.loadService();
        String endpoints = "localhost:8081";
        ClientConfiguration config = ClientConfiguration.newBuilder().setEndpoints(endpoints).build();

        SimpleConsumer consumer = provider.newSimpleConsumerBuilder()
                .setClientConfiguration(config)
                .setConsumerGroup("simple_consumer_group")
                .setAwaitDuration(Duration.ofSeconds(10))
                .build();

        consumer.subscribe("TopicTest", new FilterExpression("*", FilterExpressionType.TAG));
        while (true) {
            List<MessageView> messages = consumer.receive(10, Duration.ofSeconds(30));
            messages.forEach(msg -> {
                System.out.println(new String(msg.getBody()));
                consumer.ack(msg); // 手动确认
            });
        }
    }
}

业务场景:还是电商,这次是秒杀活动。1秒钟可能有5万请求,得保证消息顺序处理(FIFO),而且处理时间不确定,有的订单验证可能要5秒。SimpleConsumer能指定等待时间和处理时长,完美适配。

优势凸显

  • 灵活性:你自己拉消息,但不像PullConsumer那么原始,可以批量拉(比如10条),还能设置等待时间(30秒),没消息就等会儿。
  • 异步友好:处理逻辑可以扔到别的线程,不像PushConsumer卡死在同步。
  • 超时可控:处理时间不够?调用API延长,不怕重发。比如5万请求,平均每条1秒,偶尔5秒的也能从容应对。

跟主流接轨

  • 云原生:SimpleConsumer用gRPC协议,轻量又跨语言,跟现在的微服务潮流一致。
  • 原子操作:拉消息和确认消费是独立的,事务性更强,重复消费风险几乎为零。

总结与展望

从PullConsumer到PushConsumer,再到SimpleConsumer,RocketMQ的Consumer进化就是从"全手动"到"半自动"再到"智能手动"的过程。PullConsumer适合小打小闹但费劲,PushConsumer省心但不够灵活,SimpleConsumer则是集大成者,兼顾了控制力和效率。

业务上,小规模日志用PullConsumer够了,实时通知选PushConsumer没毛病,高并发复杂场景就得上SimpleConsumer。优化的路子无非是减轻客户端负担、提升灵活性、保证可靠性,这些正好跟现在的分布式系统设计不谋而合,比如用异步解耦、用协议标准化、用服务端计算offset。

相关推荐
Postkarte不想说话6 分钟前
Jupyter Lab安装
后端
fliter9 分钟前
在 Async Rust 中实现请求合并(Request Coalescing)
后端
王立志_LEO9 分钟前
Gunicorn 启动django服务
后端
fliter10 分钟前
一个让我调试一周的 Rust match 陷阱
后端
一只大袋鼠21 分钟前
SpringBoot 初学阶段知识点汇总(一)
spring boot·笔记·后端
Rust研习社24 分钟前
Rust 官方拟定 LLM 政策,防止 LLM 污染开源社区?
开发语言·后端·ai·rust·开源
无风听海40 分钟前
ASP.NET Core Minimal API 深度解析
后端·asp.net
IT_陈寒1 小时前
Java的finally块竟然不是你想的那个finally!
前端·人工智能·后端
zb200641201 小时前
Laravel4.x核心特性全解析
spring boot·后端·php·laravel