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

news/2024/7/23 23:48:21

前因

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

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

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

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

第一步:找到错误信息

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

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

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

dubbo version: 2.7.3, current host: 192.168.8.224 java.lang.ClassCastException: java.util.HashMap cannot be cast to com.cxytiandi.kittycloud.user.api.request.Address

第二步:排查报错的代码

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

public ResponseData<String> 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导致的错误。

参数代码:

@Data
public class UserLoginRequest implements Serializable {
    private String username;
    private String pass;
    private List<Address> address;
}
@Data
@AllArgsConstructor
public class Address implements Serializable {
    private int status;
}

第三步:本地复现错误

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

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

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

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

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

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

第四步:排查调用方代码

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

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

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

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

第五步:BeanUtils问题排查

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

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

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

value是新的集合对象,invoke后整个address就变了。

第六步:Dubbo解码问题排查

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

结局

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


http://www.niftyadmin.cn/n/775340.html

相关文章

七牛云服务器获取Token代码【java】

目的&#xff1a;获取七牛云需要的token 简单说明&#xff1a;本来服务器只需要返回一个 token 就好了&#xff0c; 但是我这里多返回了一个 UUID 的字符串。我是把它当作文件的名称&#xff0c;防止重复&#xff0c;不想用&#xff0c;也可以不用。 主要代码&#xff08;下面三…

每日一技|巧用 Telnet 调试 Dubbo 服务

个人博客地址 studyidea.cn,点击查看更多原创文章 0x00. 前言 想象这样一个场景&#xff0c;线上某个服务突发异常&#xff0c;导致上游服务调用异常&#xff0c;数据处于中间状态。服务恢复之后&#xff0c;我们需要修复这笔数据至正常状态&#xff0c;怎么办&#xff1f; 如…

springboot2.0 配置上传文件大小 【application.yml/application.properties】

目的&#xff1a;在springboot2.0上面配置上传文件大小 1、application.properties spring.servlet.multipart.max-file-size50Mb spring.servlet.multipart.max-request-size200Mb 2、application.yml spring:servlet:multipart:enabled: truemax-file-size: 20MBmax-req…

使用NetBenchmark压测TCP,HTTP和Websocket服务

NetBenchmark是针对网络服务压测订制的开源组件&#xff0c;组件提供TCP,HTTP和Websocket的压力测试基础功能&#xff1b;为了更好的符合业务需求组件不提供&#xff35;&#xff29;配置信息源的方式(毕竟这种方式只能作有限制测试),而是由使用者来自己制定相应的逻辑代码来进…

wangeditor 跨域 使用 SpringBoot,上传文件到七牛云

1、解决跨域问题 https://blog.csdn.net/Tomwildboar/article/details/82901514 2、前台页面 wangeditorInit() { //初始化编辑器var editor new E(#div1, #div2) // 两个参数也可以传入 elem 对象&#xff0c;class 选择器// 自定义表情// editor.customConfig.emotion…

wangeditor3 上传图片到七牛云

目的&#xff1a;wangeditor3 上传图片到七牛云 很遗憾&#xff0c;我没有完成这个功能。 我复制了&#xff0c;文档上面的代码&#xff0c;就是不行。页面报异常说是没有 uploader 这个方法。我也引入了 qiniu.js 和 plupload.full.min.js。 然后我去翻看 qiniu.js 发现&am…

深入分析Java反射(一)-核心类库和方法

前提 Java反射的API在JavaSE1.7的时候已经基本完善&#xff0c;但是本文编写的时候使用的是Oracle JDK11&#xff0c;因为JDK11对于sun包下的源码也上传了&#xff0c;可以直接通过IDE查看对应的源码和进行Debug。 本文主要介绍反射的基本概念以及核心类Class、Constructor、…

使用 pagehelper 遇到的坑

pagehelper 是一款优秀开源免费的 后端分页框架。 在使用的时候&#xff0c;一定要记住不要在 后面 加 &#xff1b; &#xff08;分号&#xff09;