热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

5查询一个小时前_上线前一个小时,dubbo这个问题可把我折腾惨了

以下文章来源于猿天地,作者尹吉欢前因那是一个月黑风高的夜晚,不管有没有圆圆的月亮,都无法解救要加班的我。这就是苦涩的人生啊!

以下文章来源于猿天地 ,作者尹吉欢

前因

那是一个月黑风高的夜晚,不管有没有圆圆的月亮,都无法解救要加班的我。这就是苦涩的人生啊!

那天正好是春节回家的日子,定了晚上的票,然后还是上线的日子。

测试在做回归测试的时候,发现一个老功能报错了,什么鬼,都没改过那块代码怎么会出问题?案件疑点重重呀。。。

为了能够早点上线,早点回家,所以这个 Bug 就显得十万火急了,因为就这一个问题,其他都没问题,解决好了就可以上线了,于是开启了破案之路。

第一步:找到错误信息

机智的我在第一时间打开了 Cat 查看具体的错误,由于当时并没有想到去写一篇文章出来,错误信息也就没有截图,后面通过模拟的操作,得到了类似的一样的错误信息如下:

17f1462a154886f21921349ee4d3555e.png

Cat错误信息

居然是类转换错误,点进去查看详细的错误信息,如下图:

229220722b83a1ab893c334f59b7bd67.png

Cat错误详情

真正有价值的错误信息如下:

dubbo version: 2.7.3, current host: 192.168.8.224 java.lang.ClassCastException:

第二步:排查报错的代码

公司代码不方便透露,下面都是模拟的代码:

public ResponseData login(UserLoginRequest loginRequest) { loginRequest.getAddress().stream().map(a -> a.getStatus()).collect(Collectors.toList());return Response.ok("xxxxxxxxx");}

问题就出在了 map 这里,从 loginRequest 参数中获取 address 是一个 List

,Address 中有 status 字段,如果是正常的对象没有问题,错误告诉我们是 HashMap 不能转换成 Address 类,也就是说参数中的 Address 变成了 HashMap 导致的错误。

参数代码:

@Datapublic class UserLoginRequest implements Serializable { private String username; private String pass; private List address;}@Data@AllArgsConstructorpublic class Address implements Serializable { private int status;}

第三步:本地复现错误

找到错误后,马上本地启动相关的两个服务,我们分别叫 A 和 B 吧,现象是 A 调用 B 的某个 RPC 接口报错。

本地启动后马上复现了错误,在报错的地方打断点看参数是否变成了 HashMap,果不其然,如下图:

e62ce83ef70f363b6ff4764b5b492c85.png

参数信息

到这里感觉有点懵,参数中明明是具体的对象类型,怎么突然就变成了 HashMap,匪夷所思。

然后想着是不是在上层什么地方出问题了,继续查看报错的上层代码,没有发现异常。然后决定在 PRC 的入口处打个断点看看是不是参数一过来就出问题了,最后经过验证确实如此,也就排除了 B 服务中对参数做了转换。

接着再看下 Dubbo 内部的参数解码,

org.apache.dubbo.rpc.protocol.dubbo.DecodeableRpcInvocation#decode(org.apache.dubbo.remoting.Channel, java.io.InputStream)。也就是请求到达 B 之后解码出来的已经是 HashMap 了,那么问题肯定是调用方传输的参数有问题。

d1dded70d5edad6d7593d3bc8b3fa68b.png

Dubbo内部参数查看

第四步:排查调用方代码

在调用方这边发起请求前,查看了参数对象,发现这个时候参数已经出问题了,字段类型发生了变化,所以问题就出在这里,都是老代码,应该都没改过,而是事实却被改了,通过 Idea 的 Annotate 快速的查看了当前方法中有被修改的记录,找到了修改的代码,下面通过模拟的方式贴出有问题的代码,如下:

&#64;Reference(version &#61; DubboConstant.VERSION_V100, group &#61; DubboConstant.DEFAULT_GROUP)private UserRemoteService userRemoteService;public void test() { UserLoginRequest request &#61; new UserLoginRequest(); request.setUsername("yjh"); request.setPass("123456"); List address &#61; new ArrayList<>(); address.add(new Address(1)); request.setAddress(address); UserLoginRequest2 request2 &#61; new UserLoginRequest2(); request2.setUsername("yjh2"); request2.setPass("1234562"); List address2 &#61; new ArrayList<>(); address2.add(new Address2(StatusEnum.INVALID)); request2.setAddress(address2); BeanUtils.copyProperties(request2, request); userRemoteService.login(request);}

出问题的就是 BeanUtils.copyProperties(request2, request); 这行代码&#xff0c;将一个对象复制到另一个对象&#xff0c;两个对象的属性都一样&#xff0c;唯一不一样的是 Address 中的 status 是 int 类型&#xff0c;Address2 中的 status 是 Enum&#xff0c;复制过去就出问题了。

7685e8210c53e1df827f4ae7be1e6cfd.png

属性复制

这种情况也只在 Dubbo 的 RPC 请求出问题&#xff0c;如果是 Http 请求&#xff0c;基本类型变成了枚举&#xff0c;直接就报错了&#xff0c;无法转换。

42d8edcbc50f6ce7971f84d1bd30836c.png

Http请求错误

第五步&#xff1a;BeanUtils 问题排查

归根到底还是 copy 的问题&#xff0c;我做了个小实验&#xff0c;如果是 Address2 copy 到 Address 是不会出问题的&#xff0c;只有嵌套的对象才会出问题。

特意看了下 copy 的代码&#xff0c;如果是 Address2 copy 到 Address&#xff0c;那么就是 status 到 status&#xff0c;在 copy 之前会进行判断 Address 的 setStatus 的第一个参数类型和 Address2 的 getStatus 的返回值是否相同&#xff0c;如果相同才会进行赋值操作&#xff0c;不同就不会&#xff0c;如果是单个对象在这里就会直接过滤掉了&#xff0c;一个是 int 一个是 Enum。

1c0285484f2916d41f47f281211f93ba.png

BeanUtils源码

嵌套对象之所以可以那是因为 address 的参数和返回类型都是 List&#xff0c;没有去判断嵌套类里面的&#xff0c;是整个集合直接复制赋值的&#xff0c;下图是目标方法&#xff1a;

2d18539da86a17d22efb0f250d2f28cd.png

BeanUtils源码

value 是新的集合对象&#xff0c;invoke 后整个 address 就变了。

869e63530d4af269fa9bcb40a8b22431.png

嵌套对象复制后

第六步&#xff1a;Dubbo 解码问题排查

前面分析中&#xff0c;调用之前通过 BeanUtils 复制&#xff0c;只是将枚举赋值给了基本类型&#xff0c;如果 Dubbo 在接收到参数进行解码时能够识别出类型不一致&#xff0c;这样就直接会报错了&#xff0c;然而并没有&#xff0c;特意调试了下 Dubbo 解码的代码&#xff0c;默认是 Hessian 的解码&#xff0c;怀疑跟 Hessian 有关&#xff0c;于是我把序列化改成了 FastJson&#xff0c;在解码参数的时候就直接报错了&#xff0c;不能转换成 int 类型。而 Hessian 在映射不了的时候就直接变成 HashMap 了&#xff0c;这才有了我们前面的错误。

e1348fe4af316f796d58ce0a7c7dd968.png

FastJson解码失败

结局

找到原因后解决就是分分钟的事了&#xff0c;通过这个问题还是说明了加任何的代码都有风险。剩下的就是开发的锅了&#xff0c;加了代码没有自测&#xff0c;好在有测试把关&#xff0c;否则就凉凉了。



推荐阅读
  • 本文讨论了一个关于cuowu类的问题,作者在使用cuowu类时遇到了错误提示和使用AdjustmentListener的问题。文章提供了16个解决方案,并给出了两个可能导致错误的原因。 ... [详细]
  • Java学习笔记之面向对象编程(OOP)
    本文介绍了Java学习笔记中的面向对象编程(OOP)内容,包括OOP的三大特性(封装、继承、多态)和五大原则(单一职责原则、开放封闭原则、里式替换原则、依赖倒置原则)。通过学习OOP,可以提高代码复用性、拓展性和安全性。 ... [详细]
  • Java太阳系小游戏分析和源码详解
    本文介绍了一个基于Java的太阳系小游戏的分析和源码详解。通过对面向对象的知识的学习和实践,作者实现了太阳系各行星绕太阳转的效果。文章详细介绍了游戏的设计思路和源码结构,包括工具类、常量、图片加载、面板等。通过这个小游戏的制作,读者可以巩固和应用所学的知识,如类的继承、方法的重载与重写、多态和封装等。 ... [详细]
  • SpringBoot uri统一权限管理的实现方法及步骤详解
    本文详细介绍了SpringBoot中实现uri统一权限管理的方法,包括表结构定义、自动统计URI并自动删除脏数据、程序启动加载等步骤。通过该方法可以提高系统的安全性,实现对系统任意接口的权限拦截验证。 ... [详细]
  • 向QTextEdit拖放文件的方法及实现步骤
    本文介绍了在使用QTextEdit时如何实现拖放文件的功能,包括相关的方法和实现步骤。通过重写dragEnterEvent和dropEvent函数,并结合QMimeData和QUrl等类,可以轻松实现向QTextEdit拖放文件的功能。详细的代码实现和说明可以参考本文提供的示例代码。 ... [详细]
  • Java序列化对象传给PHP的方法及原理解析
    本文介绍了Java序列化对象传给PHP的方法及原理,包括Java对象传递的方式、序列化的方式、PHP中的序列化用法介绍、Java是否能反序列化PHP的数据、Java序列化的原理以及解决Java序列化中的问题。同时还解释了序列化的概念和作用,以及代码执行序列化所需要的权限。最后指出,序列化会将对象实例的所有字段都进行序列化,使得数据能够被表示为实例的序列化数据,但只有能够解释该格式的代码才能够确定数据的内容。 ... [详细]
  • Java容器中的compareto方法排序原理解析
    本文从源码解析Java容器中的compareto方法的排序原理,讲解了在使用数组存储数据时的限制以及存储效率的问题。同时提到了Redis的五大数据结构和list、set等知识点,回忆了作者大学时代的Java学习经历。文章以作者做的思维导图作为目录,展示了整个讲解过程。 ... [详细]
  • eclipse学习(第三章:ssh中的Hibernate)——11.Hibernate的缓存(2级缓存,get和load)
    本文介绍了eclipse学习中的第三章内容,主要讲解了ssh中的Hibernate的缓存,包括2级缓存和get方法、load方法的区别。文章还涉及了项目实践和相关知识点的讲解。 ... [详细]
  • XML介绍与使用的概述及标签规则
    本文介绍了XML的基本概念和用途,包括XML的可扩展性和标签的自定义特性。同时还详细解释了XML标签的规则,包括标签的尖括号和合法标识符的组成,标签必须成对出现的原则以及特殊标签的使用方法。通过本文的阅读,读者可以对XML的基本知识有一个全面的了解。 ... [详细]
  • 个人学习使用:谨慎参考1Client类importcom.thoughtworks.gauge.Step;importcom.thoughtworks.gauge.T ... [详细]
  • 开发笔记:实验7的文件读写操作
    本文介绍了使用C++的ofstream和ifstream类进行文件读写操作的方法,包括创建文件、写入文件和读取文件的过程。同时还介绍了如何判断文件是否成功打开和关闭文件的方法。通过本文的学习,读者可以了解如何在C++中进行文件读写操作。 ... [详细]
  • 本文介绍了在Python3中如何使用选择文件对话框的格式打开和保存图片的方法。通过使用tkinter库中的filedialog模块的asksaveasfilename和askopenfilename函数,可以方便地选择要打开或保存的图片文件,并进行相关操作。具体的代码示例和操作步骤也被提供。 ... [详细]
  • Iamtryingtomakeaclassthatwillreadatextfileofnamesintoanarray,thenreturnthatarra ... [详细]
  • VScode格式化文档换行或不换行的设置方法
    本文介绍了在VScode中设置格式化文档换行或不换行的方法,包括使用插件和修改settings.json文件的内容。详细步骤为:找到settings.json文件,将其中的代码替换为指定的代码。 ... [详细]
  • 判断数组是否全为0_连续子数组的最大和的解题思路及代码方法一_动态规划
    本文介绍了判断数组是否全为0以及求解连续子数组的最大和的解题思路及代码方法一,即动态规划。通过动态规划的方法,可以找出连续子数组的最大和,具体思路是尽量选择正数的部分,遇到负数则不选择进去,遇到正数则保留并继续考察。本文给出了状态定义和状态转移方程,并提供了具体的代码实现。 ... [详细]
author-avatar
梦春情初开
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有