生产rabbitMq消费者莫名其妙消失
原因: 业务方发现2个系统数据一致性不对,立马就联系了我们技术人员,我们这两个系统通过 MQ 进行解耦的。所以第一时间是查看消息是否积压。发现mq中有500多条消息,数量不多,但是为什么没有消费那?仔细一看,这个队列没有消费者!
那么问题来了?同一个应用,其他队列的消费者都还在,只有这个队列的消费者丢失。
第一时间重启应用不要耽误业务时间,重启后查看日志发现消费者在一直反复的消费第一条消息,然后又消失了。发现日志里存在大量的 \ ,数据应该是正常的 json串。消费者线程消失的原因就是这里了,消息不正确一直消费不了。
查看应用打印的日志发现问题: 因为消息太大了,导致 OOM 消费者线程中断了。rabbitMq 消费者小时的原因找到了。
o.s.a.r.l.SimpleMessageListenerContainer Line:1636 1636 - Consumer thread error, thread abort.
java.lang.OutOfMemoryError: Java heap space
at java.util.Arrays.copyOf(Arrays.java:3332)
at java.lang.AbstractStringBuilder.ensureCapacityInternal(AbstractStringBuilder.java:124)
at java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:649)
at java.lang.StringBuilder.append(StringBuilder.java:202)
at org.apache.http.client.utils.URLEncodedUtils.format(URLEncodedUtils.java:357)
at org.apache.http.client.utils.URLEncodedUtils.format(URLEncodedUtils.java:334)
at org.apache.http.client.entity.UrlEncodedFormEntity.<init>(UrlEncodedFormEntity.java:58)
新问题来了
为什么?原本正常的json串会夹杂那么多的 \ ?
通过仔细查看消息属性发现,正常的消息属性 这里应该是 user类,但是带 \ 的消息 类型都是String
正确的
错误的
这时候就可以肯定,这个消息不是上层系统发过来的,那么这个队列的消息还有其他生产者吗?
@RabbitListener(queues = StaticConstant.WRITE_BACK_SCAN_FINISH_QUEUE,containerFactory = "sendScanInfoToOmsContainerFactory")
public void onMessage(Message message, Channel channel) throws Exception {
String body = "";
try {
body = new String(message.getBody(),"utf-8");
// 正常业务逻辑
} catch (Exception e) {
// 如果出现异常重新丢回队列
if(!StringUtils.isEmpty(body)){
rabbitTemplate.convertAndSend(body);
}
}
}
通过仔细检查代码发现,接受到的 body 又被丢到队尾了。这时已经可以知道了,因为他直接用了 rabbitTemplate重新丢的,rabbitTemplate 使用的 Jackson2JsonMessageConverter 序列化处理器。json处理String的时候回自动在两边加 " " , 因为之前的map转的json里面已经带有 “ 了,这时候会对body字符串里的 “ 进行转义处理。
举个例子:
一个简单的例子,新建一个map 然后转成json串,模拟成我们MQ 中的body,然后我们接受到,再使用toJsonString 再序列化一次
public static void main(String[] args) {
HashMap<Object, Object> hashMap = Maps.newHashMap();
hashMap.put("A", 1);
hashMap.put("B", 2);
hashMap.put("C", 3);
String body = JSON.toJSONString(hashMap);
System.out.println("body====="+body);
System.out.println("body序列化之后====="+JSON.toJSONString(body));
}
控制台
body====={"A":1,"B":2,"C":3}
body序列化之后====="{\"A\":1,\"B\":2,\"C\":3}"
查看控制台结果,和分析一致。
因为消息一致循环被丢到队尾导致后面转移字符成倍增长,很快消费者就会被卡掉。
查看json源码
com.alibaba.fastjson.serializer.SerializeWriter#write('"'); 在前后分别加了 ”,通过设置可以改为单引号 '
还是用之前的例子实验一下,用单引号可以暂时解决问题。
控制台输出:
body====={"A":1,"B":2,"C":3}
body序列化之后====='{"A":1,"B":2,"C":3}'
解决方法
既然我们知道是序列化的问题了,线上继续解决问题,我采用 反序列化方法。逻辑很简单,具体看代码
//body是消息体
// 临时处理 线上问题
while (body.contains("\\\\")){
body= com.alibaba.fastjson.JSON.parseObject(body, String.class);
}
body= com.alibaba.fastjson.JSON.parseObject(body, String.class);
因为我们的消息体中,本身消息文本可能存在 \ ,所以while条件使用 两个 \\,然后循环出来,再手动反序列化一次,就可以处理成原始正常数据