RocketMq 部分队列不能消费问题排查

事件回放

晚上8:40左右,测试反馈测试环境的业务不正常,经过排查,发送MQ都没有收到,但是生产者那边的MQ确实已经发出来了,rocketMq的控制台也能查到对应的这条消息。

第一条发现:

在查看消息详情的时候,发现这条消息对应的consumer的TREAD_TYPE NOT_CONSUMER_YET , NOT_CONSUMER_YET 表示这条消息确确实实存在,但是没有被消费。

没有被消费的情况有很多种, 比如说:代码里面出现了异常,没有正确的返回消费状态,那么这条消息可能就不会被消费。 还有就是消费组有问题,队列没有正常分配。

排查过程

通过查看日志代码,没有明显的Excepetion抛出来,但是接收MQ的日志完全没有打出来,继续查看rocketMq 控制台的消费组信息。

发现了一个非常诡异的地方,一个clientId对应了两个clientAddr , 理论上来说,这是不可能的,因为代码里面只启动了一个消费者.

查看队列消费状态

从图中可以看到,消费组有两个实例,但是队列分配的时候,有一半的队列没有分配到处理实例,所以问题出现的原因已经很明显了,就是因为有些队列没有分配到处理实例,导致消息一直没有被消费。

那么是因为什么原因导致的呢? 我们从源码的角度上看一看rocketMq的队列负载均衡策略

负载均衡算法

分配算法有下面6种,默认的是AllocateMessageQueueAveragely, 平均分配策略

平均分配策略(默认)(AllocateMessageQueueAveragely)
环形分配策略(AllocateMessageQueueAveragelyByCircle)
手动配置分配策略(AllocateMessageQueueByConfig)
机房分配策略(AllocateMessageQueueByMachineRoom)
一致性哈希分配策略(AllocateMessageQueueConsistentHash)
靠近机房策略(AllocateMachineRoomNearby)

我们看一下AllocateMessageQueueAveragely的圆满。

consumerGroup : 消费组

currentCID:当前实例的ID

mqAll: 所有的队列

cidAll: 当前消费组里面的所有的实例

@Override
    public List<MessageQueue> allocate(String consumerGroup, String currentCID, List<MessageQueue> mqAll,
        List<String> cidAll) {
        if (currentCID == null || currentCID.length() < 1) {
            throw new IllegalArgumentException("currentCID is empty");
        }
        if (mqAll == null || mqAll.isEmpty()) {
            throw new IllegalArgumentException("mqAll is null or mqAll empty");
        }
        if (cidAll == null || cidAll.isEmpty()) {
            throw new IllegalArgumentException("cidAll is null or cidAll empty");
        }

        List<MessageQueue> result = new ArrayList<MessageQueue>();
        if (!cidAll.contains(currentCID)) {
            log.info("[BUG] ConsumerGroup: {} The consumerId: {} not in cidAll: {}",
                consumerGroup,
                currentCID,
                cidAll);
            return result;
        }
				// 重点在这个地方,通过当前实例ID,取消费组里面的第一个节点。
        int index = cidAll.indexOf(currentCID);
        // 求出每个实例平均分配的队列数
        int mod = mqAll.size() % cidAll.size();
        int averageSize =
            mqAll.size() <= cidAll.size() ? 1 : (mod > 0 && index < mod ? mqAll.size() / cidAll.size()
                + 1 : mqAll.size() / cidAll.size());
        int startIndex = (mod > 0 && index < mod) ? index * averageSize : index * averageSize + mod;
        int range = Math.min(averageSize, mqAll.size() - startIndex);
        for (int i = 0; i < range; i++) {
            result.add(mqAll.get((startIndex + i) % mqAll.size()));
        }
        return result;
    }

说明:

从我们上面的现象可以得知,一个消费组中,当CID相同的情况下,只会有第一个可以实际分配到处理队列, 但是由于每个实例分配的队列数,是通过。队列总数%实例总数得到的,这就导致了。有一个消费组实例是没有处理队列的。

问题:

是什么原因导致的一个应用启了两个实例呢? 首先怀疑的是jar冲突之类的导致程序运行不正常,但是通过依赖工具分析,没有发现异常。

于是去查看tomcat的热部署机制,因为热部署机制也是会导致应用被发布两次。

 <Host name="localhost"  appBase="webapps"
            unpackWARs="true" autoDeploy="false">

很遗憾,tomcat的autoDeploy是false, 这个问题排除。

接下来去webapps的文件夹里面查询运行的源代码,发现了问题所在

tomcat外面运行了一套代码,ROOT里面运行一套代码,这就导致了一个tomcat里面运行了两个应用。删除外层的应用,保留ROOT根目录, 重启tomcat ,问题解决

  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值