消息消费模式(推 or 拉)

推(Push)模式

特点

  • 由 Broker 主动将消息推送给消费者
  • 实时性较高,消息到达后立即推送给消费者
  • 消费者需要设置监听器(MessageListener)来处理推送过来的消息

实现原理

RocketMQ 的 Push 模式实际上是基于Pull模式的封装,内部实现了一个长轮询机制:

  1. 消费者启动后向 Broker 注册
  2. Broker 在有新消息时会主动推送给消费者
  3. 如果没有消息,Broker 会保持连接一段时间(默认15秒),期间如果有消息到达立即推送
  4. 如果超时仍无消息,返回空响应,客户端立即重新发起一个新的请求(又是15s)

优点

  • 使用简单,开发者只需关注业务处理逻辑
  • 实时性好,消息能快速被消费
  • Broker 自动管理 offset,简化开发

缺点

  • 消费者消费速度可能跟不上推送速度,导致消息堆积
  • 客户端需要处理好流控

代码示例

// 1. 创建Push消费者实例,指定消费者组名
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("push_consumer_group");
// 2. 指定NameServer地址
consumer.setNamesrvAddr("127.0.0.1:9876");
// 3. 订阅Topic和Tag(*表示所有Tag)
consumer.subscribe("TestTopic", "*");
// 4. 注册消息监听器
consumer.registerMessageListener(new MessageListenerConcurrently() {
    @Override
    public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs,
                                                    ConsumeConcurrentlyContext context) {
        // 处理消息
        for (MessageExt msg : msgs) {
            System.out.printf("收到消息: %s, Body: %s %n", 
                    msg.getMsgId(), new String(msg.getBody()));
        }
        // 返回消费状态(成功)
        return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
    }
});

// 5. 启动消费者
consumer.start();

拉(Pull)模式

特点

  • 由消费者主动从 Broker 拉取消息
  • 消费者需要自己管理 offset
  • 消费者可以控制拉取的节奏和批量大小

实现方式

消费者需要手动编写拉取逻辑:

  1. 指定要拉取的 Topic 和队列
  2. 记录并管理消费位移(offset)
  3. 控制拉取频率和批量大小

优点

  • 消费速度完全由消费者控制
  • 可以灵活控制拉取节奏,适应不同场景
  • 可以精确控制消费进度

缺点

  • 实现复杂度高,需要自己管理offset
  • 实时性不如 Push 模式
  • 如果拉取频率控制不好,可能增加系统负载

代码示例

import org.apache.rocketmq.client.consumer.DefaultMQPullConsumer;
import org.apache.rocketmq.client.consumer.PullResult;
import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.common.message.MessageExt;
import org.apache.rocketmq.common.message.MessageQueue;

import java.util.HashMap;
import java.util.Map;
import java.util.Set;

public class PullConsumerExample {
    // 用于存储各队列的消费进度
    private static final Map<MessageQueue, Long> offsetTable = new HashMap<>();
    
    public static void main(String[] args) throws MQClientException {
        // 1. 创建Pull消费者实例,指定消费者组名
        DefaultMQPullConsumer consumer = new DefaultMQPullConsumer("pull_consumer_group");
        
        // 2. 指定NameServer地址
        consumer.setNamesrvAddr("127.0.0.1:9876");
        
        // 3. 启动消费者
        consumer.start();
        
        // 4. 获取指定Topic的所有消息队列
        Set<MessageQueue> mqs = consumer.fetchSubscribeMessageQueues("TestTopic");
        
        // 5. 遍历队列并拉取消息
        for (MessageQueue mq : mqs) {
            System.out.printf("从队列 %s 拉取消息%n", mq);
            
            SINGLE_MQ:
            while (true) {
                try {
                    // 6. 获取当前队列的消费进度
                    long offset = getMessageQueueOffset(mq);
                    
                    // 7. 从Broker拉取消息
                    PullResult pullResult = consumer.pullBlockIfNotFound(
                            mq,          // 消息队列
                            null,        // 子表达式(Tag过滤)
                            offset,      // 开始拉取的offset
                            32);         // 每次拉取的最大消息数
                    
                    // 8. 处理拉取结果
                    switch (pullResult.getPullStatus()) {
                        case FOUND:     // 找到消息
                            for (MessageExt msg : pullResult.getMsgFoundList()) {
                                System.out.printf("拉取到消息: %s, Body: %s %n", 
                                        msg.getMsgId(), new String(msg.getBody()));
                            }
                            // 更新消费进度
                            putMessageQueueOffset(mq, pullResult.getNextBeginOffset());
                            break;
                        case NO_MATCHED_MSG:  // 没有匹配的消息
                            break;
                        case NO_NEW_MSG:      // 没有新消息
                            break SINGLE_MQ;
                        case OFFSET_ILLEGAL:  // offset非法
                            break;
                        default:
                            break;
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
        
        // 9. 关闭消费者
        consumer.shutdown();
    }
    
    // 获取指定队列的消费进度
    private static long getMessageQueueOffset(MessageQueue mq) {
        Long offset = offsetTable.get(mq);
        if (offset != null) {
            return offset;
        }
        return 0;  // 默认从0开始
    }
    
    // 更新指定队列的消费进度
    private static void putMessageQueueOffset(MessageQueue mq, long offset) {
        offsetTable.put(mq, offset);
    }
}

对比总结

特性 Push模式 Pull模式
实时性 取决于拉取频率
复杂度 低(框架封装) 高(需自己管理offset)
流量控制 由框架实现 完全由消费者控制
适用场景 大部分常规场景 需要精细控制消费节奏的场景
消息堆积风险 可能(消费速度跟不上推送速度) 较小(消费速度可控)

注意事项

  1. Push模式
    • 更简单易用,适合大多数场景
    • 内部实际是基于Pull模式的长轮询实现
    • 需要处理好消息监听器的逻辑,避免消息堆积
  2. Pull模式
    • 需要自己管理offset(示例中使用内存存储,生产环境应持久化)
    • 需要控制好拉取频率,避免频繁请求
    • 在新版RocketMQ中,Pull模式API已被标记为@Deprecated,推荐使用SimpleConsumer
  3. 通用配置
    • 生产环境需要配置合理的NameServer地址
    • 注意消费者组名的唯一性
    • 根据业务需求设置合适的Topic和Tag
  4. 版本兼容性
    • 上述示例基于RocketMQ 4.x版本
    • 如果是5.x版本,Pull模式推荐使用新的SimpleConsumer API
posted @ 2025-07-04 18:19  CyrusHuang  阅读(17)  评论(0)    收藏  举报