23

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

 4 years ago
source link: http://mp.weixin.qq.com/s?__biz=MzIwMDY0Nzk2Mw%3D%3D&%3Bmid=2650321016&%3Bidx=1&%3Bsn=41b87640cd693a20c9f6e11799428be5
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

前因

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

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

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

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

第一步:找到错误信息

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

yaAJbej.png!webCat错误信息

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

UVrMR3q.jpg!webCat错误详情

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

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,果不其然,如下图:

6JVz6nI.png!web参数信息

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

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

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

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

AvayEf3.png!webDubbo内部参数查看

第四步:排查调用方代码

在调用方这边发起请求前,查看了参数对象,发现这个时候参数已经出问题了,字段类型发生了变化,所以问题就出在这里,都是老代码,应该都没改过,而是事实却被改了,通过 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,复制过去就出问题了。

viYvEfb.png!web属性复制

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

RBJbEzA.jpg!webHttp请求错误

第五步:BeanUtils 问题排查

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

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

iIFNN3V.png!webBeanUtils源码

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

qaiiQj3.png!web BeanUtils源码

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

V3iAba2.png!web嵌套对象复制后

第六步:Dubbo 解码问题排查

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

Aj2Yj2M.png!webFastJson解码失败

结局

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

热文推荐

Spring Boot 与微服务从0到1的实践

我在外包公司做增删改查有前途么?

那天晚上和@FeignClient注解的深度交流

jMjIJ3v.jpg!webvAjYjmU.jpg!web

UjmyInZ.jpg!web

如有收获,点个在看,诚挚感谢


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK