• RocketMQ 消费者核心配置和核心知识


    一、RocketMQ4.X 消费者核心配置

    • consumeFromWhere 配置(某些情况失效:参考https://blog.csdn.net/a417930422/article/details/83585397这个配置基本不用改,采用默认配置即可。
      • CONSUME_FROM_FIRST_OFFSET: 初次从消息队列头部开始消费,即历史消息(还储存在 broker 的)全部消费一遍,后续再启动接着上次消费的进度开始消费。
      • CONSUME_FROM_LAST_OFFSET: 默认策略,初次从该队列最尾开始消费,即跳过历史消息,后续再启动接着上次消费的进度开始消费。
      • CONSUME_FROM_TIMESTAMP:从某个时间点开始消费,默认是半个小时以前,后续再启动接着上次消费的进度开始消费。
    • allocateMessageQueueStrategy:负载均衡策略算法,即消费者分配到 queue 的算法
      • 默认值是 AllocateMessageQueueAveragely 即取模平均分配
    • offsetStore:消息消费进度存储器 offsetStore 有两个策略:LocalFileOffsetStore 和 RemoteBrokerOffsetStore 
      • 广播模式默认使用 LocalFileOffsetStore, 集群模式默认使用 RemoteBrokerOffsetStore
    • consumeThreadMax:最大消费线程池数量
    • consumeThreadMin:最小消费线程池数量
    • pullBatchSize:消费者去 broker 拉取消息时,一次拉取多少条。可选配置
    • consumeMessageBatchMaxSize:单次消费时一次性消费多少条消息,批量消费接口才有用,可选配置
    • messageModel:消费者消费模式
      • CLUSTERING:集群模式(默认配置)
      • BROADCASTING:广播模式

     二、集群和广播模式下 RocketMQ 消费端处理

    Topic 下队列的奇偶数会影响 Customer 个数里面的消费数量

    • 如果是4个队列,8个消息,4个节点则会各消费2条,如果不对等,则负载均衡会分配不均。
    • 如果 consumer 实例的数量比 message queue 的总数量还多的话,多出来的 consumer 实例将无法分到 queue,也就无法消费到消息,也就无法起到分摊负载的作用,所以需要控制让 queue 的总数量大于等于 consumer 的数量。

    集群模式(默认):

    • Consumer 实例平均分摊消费生产者发送的消息
    • 例子:订单消息,一般是只被消费一次(被标记为同一个 ConsumerGroup 组的消费者不会对消息重复消费)

    广播模式:

    • 广播模式下消费消息:投递到 Broker 的消息会被每个 Consumer 进行消费,一条消息被多个 Consumer 消费,广播消费中 ConsumerGroup 暂时无用。
    • 例子:群公告,每个人都需要消费这个消息

    怎么切换模式:通过 setMessageModel()

    三、RocketMQ 里面的 Tag 作用和消息过滤原理

    一个 Message 只有一个 Tag,Tag 是二级分类。过滤分为 Broker 端和 Consumer 端过滤。

    • Broker 端过滤,减少了无用的消息的进行网络传输,增加了 broker 的负担
    • Consumer 端过滤,完全可以根据业务需求进行过滤,但是增加了很多无用的消息传输

    一般是监听 * ,或者指定 tag,|| 运算,SLQ92,FilterServer 等;

    • Tag 性能高,逻辑简单
    • SQL92 性能差点,支持复杂逻辑(只支持 PushConsumer 中使用) MessageSelector.bySql
      • 语法:> ,<,=,IS NULL,AND,OR,NOT 等,sql where 后续的语法即可(大部分)

     生产者

    @RequestMapping("/api/v1/pay_cb")
    public Object callback( String tag, String amount) throws Exception {
    
        Message message = new Message(JmsConfig.TOPIC,tag, "",tag.getBytes());
        // 设置属性,用于sql过滤
        message.putUserProperty("amount",amount);
        
        SendResult sendResult =  payProducer.getProducer().send(message);
        System.out.printf("发送结果=%s, sendResult=%s 
    ", sendResult.getSendStatus(), sendResult.toString());
        return new HashMap<>();
    }

    消费者

    package net.xdclass.xdclassmq.jms;
    
    import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer;
    import org.apache.rocketmq.client.consumer.MessageSelector;
    import org.apache.rocketmq.client.consumer.listener.*;
    import org.apache.rocketmq.client.exception.MQClientException;
    import org.apache.rocketmq.common.consumer.ConsumeFromWhere;
    import org.apache.rocketmq.common.message.Message;
    import org.apache.rocketmq.common.message.MessageExt;
    import org.apache.rocketmq.common.protocol.heartbeat.MessageModel;
    import org.springframework.stereotype.Component;
    
    import java.io.UnsupportedEncodingException;
    import java.util.List;
    
    @Component
    public class PayConsumer {
    
    
        private DefaultMQPushConsumer consumer;
    
        private String consumerGroup = "pay_consumer_group";
    
        public  PayConsumer() throws MQClientException {
    
            consumer = new DefaultMQPushConsumer(consumerGroup);
            consumer.setNamesrvAddr(JmsConfig.NAME_SERVER);
            consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET);
            //默认是集群方式,可以更改为广播,但是广播方式不支持重试
            consumer.setMessageModel(MessageModel.CLUSTERING);
            //多标签订阅
            //consumer.subscribe(JmsConfig.TOPIC, "order_pay || order_finish || order_create");
    
            //根据sql语法进行过滤消息
            consumer.subscribe(JmsConfig.TOPIC, MessageSelector.bySql(" amount > 5 "));
    
            consumer.registerMessageListener( new MessageListenerConcurrently() {
    
                @Override
                public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs, ConsumeConcurrentlyContext context) {
                    MessageExt msg = msgs.get(0);
    
                    try {
                    System.out.printf("%s 2 Receive New Messages: %s %n", Thread.currentThread().getName(), new String(msgs.get(0).getBody()));
    
                    return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
    
                } catch (Exception e) {
                    System.out.println("消费异常");
                    e.printStackTrace();
                    return ConsumeConcurrentlyStatus.RECONSUME_LATER;
                }
                }
            });
    
            consumer.start();
            System.out.println("consumer start ...");
        }
    
    }

    注意:消费者订阅关系要一致,不然会消费混乱,甚至消息丢失。订阅关系一致:订阅关系由 Topic 和 Tag 组成,同一个 group name,订阅的 Topic 和 Tag 必须是一样的。

    在 Broker 端进行 MessageTag 过滤原理:遍历 message queue 存储的 message tag 和 订阅传递的 tag 的 hashcode 是否一样,不一样则跳过,符合的则传输给 Consumer,在 consumer queue 存储的是对应的 hashcode,对比也是通过 hashcode 对比;Consumer 收到过滤消息后也会进行匹配操作,但是是对比真实的 message tag 而不是 hashcode。

    • consume queue 存储使用 hashcode 定长,节约空间
    • 过滤中不访问 commit log,可以高效过滤
    • 如果存在 hash 冲突,Consumer 端可以进行再次确认

    建议:单一职责,多个队列;如果想使用多个 Tag,可以使用 sql 表达式,但是不建议。

    常见错误:

    The broker does not support consumer to filter message by SQL92
    
    解决:broker.conf 里面配置如下
    enablePropertyFilter=true
    
    备注,修改之后要重启 Broker
    master 节点配置:vim conf/2m-2s-async/broker-a.properties
    slave 节点配置:vim conf/2m-2s-async/broker-a-s.properties

    四、PushConsumer/PullConsumer 消费消息模式

    Push 和 Pull 优缺点分析

    • Push:实时性高;但增加服务端负载,消费端能力不同,如果 Push 推送过快,消费端会出现很多问题
    • Pull:消费者从 Server 端拉取消息,主动权在消费者端,可控性好;但间隔时间不好设置,间隔太短,则空请求,浪费资源;间隔时间太长,则消息不能及时处理
    • 长轮询: Client 请求 Server 端也就是 Broker 的时候, Broker 会保持当前连接一段时间,默认是15s,如果这段时间内有消息到达,则立刻返回给 Consumer;没消息的话,超过15s,则返回空,再进行重新请求;主动权在 Consumer 中,Broker 即使有大量的消息也不会主动推送给 Consumer。 缺点:服务端需要保持 Consumer 的请求,会占用资源,需要客户端连接数可控,否则会存在一堆连接

    PushConsumer 本质是长轮训

    • 系统收到消息后自动处理消息和 offset,如果有新的 Consumer 加入会自动做负载均衡,
    • 在 broker 端可以通过 longPollingEnable=true 来开启长轮询
    • 虽然是 push,但是代码里面大量使用了pull,是因为使用长轮训方式达到 push 效果,既有 pull 有的,又有 push 的实时性
    • 优雅关闭:主要是释放资源和保存 Offset, 调用 shutdown() 即可 ,参考 @PostConstruct、@PreDestroy

    PullConsumer 需要自己维护 Offset(参考官方例子)

    • 官方源码包例子路径:org.apache.rocketmq.example.simple.PullConsumer
    • 获取 MessageQueue 遍历
    • 客户维护 Offset,需用用户本地存储 Offset,存储内存、磁盘、数据库等
    • 处理不同状态的消息 FOUND、NO_NEW_MSG、OFFSET_ILLRGL、NO_MATCHED_MSG、4种状态
    • 灵活性高可控性强,但是编码复杂度会高
    • 优雅关闭:主要是释放资源和保存 Offset,需用程序自己保存好 Offset,特别是异常处理的时候
  • 相关阅读:
    理解HTTP的POST和PUT的区别
    眼见为实 — CSS的overflow属性
    Iconfont的代码使用
    JSP中contentType、pageEncoding和meta charset的区别
    在 webpack 中使用 ECharts
    MVC 中的 ViewModel
    一个简单例子理解C#的协变和逆变
    C#中使用委托、接口、匿名方法、泛型委托实现加减乘除算法
    c#打包文件解压缩
    8种主要排序算法的C#实现 (二)
  • 原文地址:https://www.cnblogs.com/jwen1994/p/12364557.html
Copyright © 2020-2023  润新知