记录排查问题过程和解决方案也算是总结排查问题方法。
Caused by: java.io.IOException: null
at com.rabbitmq.client.impl.AMQChannel.wrap(AMQChannel.java:126)
at com.rabbitmq.client.impl.AMQChannel.wrap(AMQChannel.java:122)
at com.rabbitmq.client.impl.AMQChannel.exnWrappingRpc(AMQChannel.java:144)
at com.rabbitmq.client.impl.ChannelN.open(ChannelN.java:133)
at com.rabbitmq.client.impl.ChannelManager.createChannel(ChannelManager.java:176)
at com.rabbitmq.client.impl.AMQConnection.createChannel(AMQConnection.java:553)
at org.springframework.amqp.rabbit.connection.SimpleConnection.createChannel(SimpleConnection.java:57)
... 20 common frames omitted
Caused by: com.rabbitmq.client.ShutdownSignalException: clean connection shutdown; protocol method: #method<connection.close>(reply-code=200, reply-text=OK, class-id=0, method-id=0)
at com.rabbitmq.utility.ValueOrException.getValue(ValueOrException.java:66)
at com.rabbitmq.utility.BlockingValueOrException.uninterruptibleGetValue(BlockingValueOrException.java:36)
at com.rabbitmq.client.impl.AMQChannel$BlockingRpcContinuation.getReply(AMQChannel.java:494)
at com.rabbitmq.client.impl.AMQChannel.privateRpc(AMQChannel.java:288)
at com.rabbitmq.client.impl.AMQChannel.exnWrappingRpc(AMQChannel.java:138)
看到有一个最关键的错误提示:
clean connection shutdown; protocol method: #method<connection.close>(reply-code=200, reply-text=OK, class-id=0, method-id=0)
直接看就是链接已经关闭,通过调用链直接定位到异常的抛出方法:
/**
* Placeholder until we address bug 15786 (implementing a proper exception hierarchy).
*/
public AMQCommand exnWrappingRpc(Method m)
throws IOException
{
try {
return privateRpc(m);
} catch (AlreadyClosedException ace) {
// Do not wrap it since it means that connection/channel
// was closed in some action in the past
throw ace;
} catch (ShutdownSignalException ex) {
throw wrap(ex);
}
}
搭上断点直接调试,发现是ShutdownSignalException
异常,抛出方法是privateRpc
内部的异常被wrap
方法包装成了一个IOException
,就有了另外一部分错误日志,这里继续深入看原始的异常信息,追踪进入方法
private AMQCommand privateRpc(Method m)
throws IOException, ShutdownSignalException
{
SimpleBlockingRpcContinuation k = new SimpleBlockingRpcContinuation(m);
rpc(m, k);
// At this point, the request method has been sent, and we
// should wait for the reply to arrive.
//
// Calling getReply() on the continuation puts us to sleep
// until the connection's reader-thread throws the reply over
// the fence or the RPC times out (if enabled)
if(_rpcTimeout == NO_RPC_TIMEOUT) {
return k.getReply();
} else {
try {
return k.getReply(_rpcTimeout);
} catch (TimeoutException e) {
throw wrapTimeoutException(m, e);
}
}
}
前后各打断点进入之后直接到了下面的代码块代码块
try {
return k.getReply(_rpcTimeout);
} catch (TimeoutException e) {
throw wrapTimeoutException(m, e);
}
这个时候基本定位出来是获取返回确认值的时候除了错误,结合上面的错误异常提示猜测是连接已经在获取确认的返回值之前断开,导致获取不到返回确认。从而有异常抛出。
因为是单元测试,并且使用过线程来法送的消息,则可能是单元测试主线程已经结束并且关闭了连接但是MQ的返回值还没有正常获取到,从而有错误抛出,尝试在单元测试中增加等待时间。
try {
TimeUnit.SECONDS.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
直接粗暴的sleep之后发现单元测试用例正常结束,不再有异常抛出。
小结:其实在debug中花了比较长时间,但是正常来说问题可以提前定位到。
这里自己查了一些资料也跟了一些源码,可能后续会配合源码写一个整个发送过程的剖析
@Bean(name = "rabbitTemplate")
public RabbitTemplate rabbitTemplate(@Qualifier("connectionFactory") ConnectionFactory connectionFactory) {
RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
rabbitTemplate.setMessageConverter(new Jackson2JsonMessageConverter());
return rabbitTemplate;
}
使用的是Jackson2JsonMessageConverter
来格式化发送的对象,内部使用的应该是Jackson,查询文档得知Jackson有注解@JsonInclude(Include.NON_NULL)
可以忽略null数据,考虑到MQ发送方需要每个对象都加注解略麻烦,必须统一全部做规范,则考虑全局处理。
自定义添加ObjectMapper
到Jackson2JsonMessageConverter
中,配置代码修改如下:
@Bean(name = "rabbitTemplate")
public RabbitTemplate rabbitTemplate(@Qualifier("connectionFactory") ConnectionFactory bpConnectionFactory) {
RabbitTemplate rabbitTemplate = new RabbitTemplate(bpConnectionFactory);
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
rabbitTemplate.setMessageConverter(new Jackson2JsonMessageConverter(objectMapper));
return rabbitTemplate;
}
至此JSON格式化问题解决。
小结:一般这种配置用的通用对象都可以设置比较多属性,可以尝试看源码属性,查文档。
解决问题时时直接看了源码里面对象有ObjectMapper属性,刚好网上查询到自定义ObjectMapper,也角色根据类名大概是一个类型转换匹配的类,则尝试查询自定义,然后修改。