rabbitmq消费者消费消息为什么变成数字了?

温馨提示:图片看不清按ctrl+鼠标滚动放大网页

  1. 问题描述

2020年11月28号,rabbitmq队列visitLog一直处于阻塞状态,重启consumer,抛出org.springframework.amqp.AmqpException: No method found for class [B。临时解决方案,将@RabbitListener注解移动到方法上:

 

 按照上图方法操作后,consumer能够消费消息,但有500多条消息消费错误,消费者在接受消息时,其中有的消息为字节数组

初次判断原因为前端发送请求参数出现了错误,在项目中,有个埋点报了如下错误:

因此,定下结论,前端发送了图上参数埋点,导致了rabbitmqvisitLog阻塞。

2020年124号,线上环境consumer消费异常,从11:30开始,不断发送错误邮件

consumer为什么接受的消息莫名其妙变成了数字呢?

2.    问题跟踪

停掉消费者,通过rabbitmq management查看,mq所接受到的消息:

如上图红框所示,content-type变成了application/json ,以前一直是text/plain。查看consumer端的代码:

接受消息为字符串类型,原因就是mq里所存的消息content-typeapplication/json,而consumer接受的消息为字符串,没有对消息进行转换,即配置额外的MessageConverter。通过查阅文档,

spring-amqp5.1.2开始已经对这个点进行了优化,即不需要配置额外的MessageConverter,原因在之后的resolveArgument环节,匹配到了RabbitListenerAnnotationBeanPostProcessor$BytesToStringConverter。这个Converter就可以将String类型Payloadbyte[]可以正常convertString字符串。

在周五临时解决方案中,升级springboot版本,并StringEscapeUtils.unescapeJava,处理掉字符串中的转义字符,最终临时上线。但是后面一直出现json解析错误。

3.    问题原因

       问题的根本原因就是消息的发送方发送消息content-type从原来的text/plain变成了application/json,并且当天临时上线解决后,content-type又变成了text/plain,而consumer只能消费text/plain的消息。

4.    代码追踪

4.1  生产者代码追踪

       查看visitLog消息的生产者:

       点击convertAndSend分析,直到doSend方法浏览代码:

       如上图所示:红框中有message.getMessageProperties(),获取消息属性,那消息属性是在哪里设置的呢?

        查看amqpTemplate配置

        查看RabbitTemplate类源码,浏览属性:
 

 

浏览方法:
 

 

看到RabbitTemplate的默认MessageConverterSimpleMessageConverter 点击SimpleMessageConverter源码:

查看createMessage方法注释,创建一个AMQP消息,从生产者;
如果消息类型为字节数组,则content-typeapplication/octet-stream
如果消息类型为字符串,则content-typetext/plain;
如果消息类型为序列化对象,则content-typeapplication/x-java-serialized-object。
查看convertAndSend发送的类型:

为字符串类型,明明是字符串类型,为什么content-type莫名其妙变成了application/json,搜索MessageConverter子类下的所有application/json,即MessageProperties类下的成员变量CONTENT_TYPE_JSON
 

 

 Jackson2JsonMessageConverterJsonMessageConverter中设置了content-typeapplication/json

由此推断,是amqpTemplate的消息转换器被更改了,全局搜索Jackson2JsonMessageConverterJsonMessageConverter,发下了如下代码:

 

果然amqpTemplateMessageConverter被更改,查看spring配置

 

MessageQueueServiceImplAgoraPushServiceImpl中使用的RabbitTemplate都是这里配置的同一个对象,springIOC注入的对象默认是单例的,故在其他地方调用sendMsgToAgora后,会将amqpTemplateMessageConverter改成Jackson2JsonMessageConverter

4.2 消费者代码追踪
               查看MessageConsumer代码,在MessageConsumer类上注解了@RabbitListener,在handleMessage上注解了@RabbitHandler,故当队列有消息时,会通过handleMessage方法来消费。那为什么会出现org.springframework.amqp.AmqpException: No method found for class [B这个异常呢? 

查询相关博客:https://blog.csdn.net/u013905744/article/details/86736536

为什么一个普通的方法加上@RabbitListener注解就能接收消息了呢?

先总结来说,有一个BeanPostProcessor来处理这个注解,把注解相关的内容取出来,封装成一个RabbitListenerEndPoint。然后给每个Endpoint创建一个MessageListenerContainer,在这个container中注册一个MessageListener,在这个MessageListener中创建了一个HandlerAdapter,这个adapterrabbitmq broker建立一个connection,接收rabbitmq broker push过来的message,放到一个blocking queue中。至此完成消息的接收。

 
根据上述博客描述内容,点开MessageListenerContainer源码:

从上面能看到设置消费者,设置队列等信息,仔细查看doStart方法:

可以看到,@Listener接受消息实现在这里。关键代码this.initializeConsumers();

 

关键代码this.taskExecutor.execute(new AsyncMessageProcessingConsumer(consumer));
点击AsyncMessageProcessingConsumer构造方法:
 

 

在点开BlockingQueueConsumer类,查看handle方法:
 

 

消费者的消息类型设置在红框中,故可以查看consumer接受消息时的content-type类型,查看MessageListenerContainer属性,使用DefaultMessagePropertiesConverter
 
点开MessageProperties
 

 

查看代码得知,消息的属性设置来源于BasicProperties,而BasicProperties中的消息来源于消息本身的属性。但这样说,消息的接受最终会和消息的发送方适配?

查阅博客:https://www.jianshu.com/p/382d6f609697

@RabbitListener可以标注在类上面,当使用在类上面的时候,需要配合@RabbitHandler注解一起使用,@RabbitListener标注在类上面表示当有收到消息的时候,就交给带有@RabbitHandler的方法处理,具体找哪个方法处理,需要跟进MessageConverter转换后的java对象。
 

@RabbitListener注解指定目标方法来作为消费消息的方法,通过注解参数指定所监听的队列或者Binding。使用@RabbitListener可以设置一个自己明确默认值的RabbitListenerContainerFactory对象

如果消息属性中没有指定content_type,则接收消息的处理方法接收类型是byte[],如果消息属性中指定content_type为text,则接收消息的处理方法的参数类型是String类型。不管有没有指定content_type,处理消息方法的参数类型是Message都不会报错

 
由此我们得知,当@RabbitListener@RabbitHandler配合使用时,会将不同的消息适配到合适的方法上,当消息content-type变成application/json时,而我们处理消息的方法只有图下方法,没有适配到其他方法,故会抛出No method found for class [B,导致队列没有消费者,致使mq阻塞。

 

@RabbitListener注解到目标方法时,此异常解决。但消息类型缺被转成一串数字?为什么呢?点开MessagingMessageListenerAdapter 

 

 

 如果没有配置MessageConverterMessagingMessageListenerAdapter使用的MessagingMessageConverter会初始化一个SimpleMessageConverter,点开SimpleMessageConverter,查看fromMessage方法:

 

 

如果contentTypetext开头,消息会初始化为字符串

content = new String(message.getBody(), encoding);

如果等于application/x-java-serialized-object,消息会反序列化

content = SerializationUtils.deserialize(this.createObjectInputStream(new ByteArrayInputStream(message.getBody()), this.codebaseUrl));

如果上面全部不满足:content = message.getBody();

最终以object方式返回,因此最终接受消息是字节数组。

5. 问题复现
①停掉开发环境consumer,清空visitLog队列
②调用/api/test/push接口
③测试/visitlog/newLog接口 
④查看消息
看到消息content-type变成application/json
⑥本地启动consumer,抛出异常
⑦将@RabbitListener注解到方法上,重启consumer,出现字节数组

 

 

 

 

6. 总结与反思
根本原因,queue的生产者与消费者content-type不一致。如果在后面代码中,消息发送方变成发送序列化对象,同样会抛出No method found for class [B,造成消息队列阻塞。
    在开发中,应该限定每个queuecontent-type,不能发送其他类型数据;同一项目一个队列应该配置唯一对应的RabbitTemplate,而不是RabbitTemplate对应多个队列。
  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值