用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。