RocketMQ消费者订阅了tag,需要注意什么?

 在RocketMQ中,一个消费组能同时订阅多个 tag,但一个消费组的不同消费者不能分开订阅不同的tag,即同一个消费组的订阅关系必须保持一样。例如:常见错误使用方式同一个项目中,一段消费代码订阅tagA,然后拷贝到这段代码再更改为tagB。

 正确用法:

public void subscribe(){
       DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("arch_online_test_consumer");
       consumer.subscribe("arch_online_test","tag1 || tag2 || tag3");
 }  错误用法:
public class SubscribeTest {
   public void subscribeA(){
     DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("arch_online_test_consumer");
     consumer.subscribe("arch_online_test","tag1");
   } 

   public void subscribeB(){
     DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("arch_online_test_consumer");
     consumer.subscribe("arch_online_test","tag2");
   } 
 }

 

发现大量的RocketMQ client 大量的info日志输出,如何禁用?

答: 尝试以下设置,项目中使用了Slf4j 

 

1、可以配置RocketmqClient的logger设置优先级为warn

 

2、也可以通过-Drocketmq.client.logUseSlf4j=false 和 -Drocketmq.client.logLevel=WARN 关闭MQ客户端使用Slf4j并提高日志等级

 

项目中没有使用Slf4j,可以通过-Drocketmq.client.logLevel=WARN调高日志等级。

我的服务消费后需要调用第三方接口,别人的接口调用有限制,Rocketmq消费可以限流吗?

 RocketMQ本身没有类似每秒消费多少条数据的精确限流,我们可以结合Sentienl来实现:

private String KEY = "arch_topic:melon_consumer"; // 资源名称由topic和消费组构成
     
     public static void main(String[] args) throws InterruptedException, MQClientException {
         initFlowControlRule(); // Sentinel流控规则
         DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("arch_consumer");
         consumer.setNamesrvAddr("localhost:9876");
         consumer.subscribe("arch_topic", "*");
         consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET);
         consumer.registerMessageListener(new MessageListenerConcurrently() {
             @Override
             public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs, ConsumeConcurrentlyContext context) {
                 for (MessageExt msg : msgs) {
                     Entry entry = null;
                     try {
                         ContextUtil.enter(KEY); // 定义资源
                         entry = SphU.entry(KEY, EntryType.OUT);
                         System.out.printf("%s Receive New Messages: %s %n", Thread.currentThread().getName(), msg);
                     } catch (BlockException ex) {
                         // Blocked.被限流后消息重试
                         return ConsumeConcurrentlyStatus.RECONSUME_LATER;
                     } finally {
                         if (entry != null) {
                             entry.exit();
                         }
                         ContextUtil.exit();
                     }
                 }
                 return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
             }
         });
         consumer.start();
         System.out.printf("Consumer Started.%n");
     }

     private static void initFlowControlRule() {
         FlowRule rule = new FlowRule();
         rule.setResource(KEY);
         rule.setCount(5);// 每秒通过5条消息
         rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
         rule.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER);
         rule.setMaxQueueingTimeMs(5 * 1000); // 排队超时时间5秒
         FlowRuleManager.loadRules(Collections.singletonList(rule));


    }

 RocketMQ默认延迟等级有18个,我可以扩增吗?

 可以的,但是不建议扩增太多等级,可以通过修改broker属性messageDelayLevel来实现,注意修改了后需要重启broker.

Caused by: com.alibaba.rocketmq.remoting.exception.RemotingTimeoutException: wait response on the channel <10.58.218.151:10910> timeout, 3000(ms)

1、这个错误生产者发送消息的出现比较频繁,发送超时;

2、业务层面:

     先确认是一台机器有问题,还是多台有问题; 如果是只有一台有问题,大概率是业务服务或者机器有问题;

     确认同时间段内段其它远程服务是否异常:比如dubbo调用、zookeeper调用、mysql调用等;

 

     服务层面: 查看时间段内的JVM相关指标,比如GC次数、GC耗时等

    机器层面: 通过falcon查看机器的load、cpu busy、disk io util、swap等

 

3、MQ层面:

    如果发生的时间段,很多业务出现该情况,大概率是Broker问题

    查看机器等load、cpu busy、disk io util 、swap、net等; 

 

公司目前没有捕捉网络抖动的工具和平台,如果真发生的网络层面的抖动,是很难排查的. 下面就是一个经典的案例:

 

【图片】

Not found the consumer group consumer stats, because return offset table is empty, maybe the consumer not consume any message

检查生产者、消费者使用客户端版本是否相同

 

新增消费组,消费起点如何设置

通过设置consumer.setConsumerFromWhere属性可以解决, 注意此属性是有在group第一次消费时生效,后续都是延续上一次消费进度offset进行消费.

 

消费被订阅了, 但是没有消费(offset过小)

根据messageID进行查询具体某个消息时, 会出现以上提示. 说明当前消息还没有被某个group消费,并且当前消息offset小于maxoffset时,会有以上提示

确认是不是顺序消费,消费失败阻塞后续消费了?

 

producer运行起来发送消息时抛出异常: No route info of this topic

1、broker上不存在该topic, 创建该topic即可; 注意: 测试环境topic都是自动创建, 偶尔会出现创建异常情况, 使用手动创建保证创建成功;

2、broker没有正确链接到name server上;

3、producer没有正确连接到name server上;

 

最佳实践建议

1、消费端幂等性验证

      rocketmq无法避免消息重复(Exactly-Once). 建议采取消息Id、业务唯一标识字段做幂等性

2、消费速度慢处理方式

     1) 提高消费并行度

     2) 跳过非重要消息

     3) 优化消息消费过程

3、其它

     1) 订阅组与topic多对一, 避免一对多

     2) 顺序消息注意异常处理, 使用ack方式替代

     3) 不建议阻塞监听器, 会导致阻塞线程池, 并最终线程池耗尽无法消费

     4) 3.x版本消息重塑需要停止该group左右消费者应用, 否则不生效

     5) 建议使用push模式, 没有特殊需求属性值尽量保证保持默认配置

 

================

控制台查询message,messageTrack提示 xxxgroup订阅了,但是被过滤掉了

比较消息和group的tag是否匹配。注意*的问题,只有在配置group订阅的时候*才有全匹配的意思,在消息在只是表示tag是字符串"*"

控制台查询message,messageTrack提示 xxxgroup订阅了,但是没有消费(Offset小)

表示还没有消费到这一条,可以查询消费者进度对比核实下

消息没有被消费。

先根据msgid查询消息所在queue和对应偏移,然后查询消费者偏移比较大小。如果消费者偏移小,说明还没消费。如果消费者偏移大说明已经越过,可以通过tag判断。也有可能是消费失败但是消费者没有记录异常。

消费者无法正常消费

消费者启动但是一直有积压无法消费可能有一下几种可能

通group订阅信息不一致导致:详情  同group订阅不同topic 无法消费的问题

InstanceName  冲突:InstanceName是队列负载均衡算法的计算依赖,相同的InstanceName会导致队列分配混乱导致部分队列消费混乱

多起的的客户端:经常发现业务在项目中多起了客户端,可以先查询客户端连接然后与业务核实

消费队列不均匀

指观察消费队列 一部分差值正常,一部分差值不正常有很多挤压。

先观察消费者队列的分配情况,之后观察消费者连接的InstanceName 是否有冲突

 

异常:RemotingTimeoutException: wait response on the channel <xxx> timeout, xxx(ms)

这个异常本质就是rpc调用超时,rocket的rpc请求都是异步的。这个异常表示发送成功后等待响应超过了最大等待时间。可能是有人发送了大消息,或者rocket发生了波动。可以适当的调整最大超时时间  setSendMsgTimeout 

 

===================

 

rocketmq nameserver配置多地址 rocketmq 多个tag_时间段

 

上图中,一个 MQ 对应有两个消费者,他们是在同一个 Group1 中,起初大家都只有 Topic1,这时候是正常消费的。

 

但如果在第一个消费者里面加入一个 Topic2,这时候是无法消费或消费不正常了。

 

这是 RocketMQ 本身的机制引起的问题,需要在第二个消费者里面加入 Topic2 才能正常消费。

 

 为什么

因为broker 端通过group的订阅配置来构建consumeQueue 。

group 订阅配置存储在

com.alibaba.rocketmq.broker.BrokerController的SubscriptionGroupManager属性中

消费消费者启动后会定时发送心跳,心跳中会带上自己的订阅配置

private void sendHeartbeatToAllBroker() {
    final HeartbeatData heartbeatData = this.prepareHeartbeatData();
    final boolean producerEmpty = heartbeatData.getProducerDataSet().isEmpty();
    final boolean consumerEmpty = heartbeatData.getConsumerDataSet().isEmpty();
    if (producerEmpty && consumerEmpty) {
        log.warn("sending hearbeat, but no consumer and no producer");
        return;
    }
 
    Iterator<Entry<String, HashMap<Long, String>>> it = this.brokerAddrTable.entrySet().iterator();
    while (it.hasNext()) {
        Entry<String, HashMap<Long, String>> entry = it.next();
        String brokerName = entry.getKey();
        HashMap<Long, String> oneTable = entry.getValue();
        if (oneTable != null) {
            for (Long id : oneTable.keySet()) {
                String addr = oneTable.get(id);
                if (addr != null) {
                    if (consumerEmpty) {
                        if (id != MixAll.MASTER_ID)
                            continue;
                    }
 
                    try {
                        this.mQClientAPIImpl.sendHearbeat(addr, heartbeatData, 3000);
                        log.info("send heart beat to broker[{} {} {}] success", brokerName, id, addr);
                        log.info(heartbeatData.toString());
                    } catch (Exception e) {
                        log.error("send heart beat to broker exception", e);
                    }
                }
            }
        }
    }
}
 
public class HeartbeatData extends RemotingSerializable {
    private String clientID;
    private Set<ProducerData> producerDataSet = new HashSet<ProducerData>();
    //消费者组的订阅配置
    private Set<ConsumerData> consumerDataSet = new HashSet<ConsumerData>();
}

 

心跳请求code为

RequestCode.HEART_BEAT = 34

broker端处理请求时会查询订阅配置和当前,是否不一致需要更新。

所以在上面的情况上,两个客户端会交替更新broker的订阅信息,而topic1怎么更新都是存在的,但是topic2则会收影响导致消费有问题