赞
踩
晚上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 ,问题解决
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。