Dubbo服务热加载踩坑记
1. 问题详情
最近进行大规模的系统测试,发现开发的一个微服务网关平台的Dubbo服务经常报错,具体报错内容如下,即名称完全相同的两个类报出“类型转换异常”:
java.lang.ClassCastException:com.xxx..UserBo cannot be cast to com.xxx..UserBo at com.xxx.UserService.queryUser(UserService.java:38) |
Dubbo消费者报错的源码如下所示(安全考虑,非真实源码):
public void queryUser(String userId){ ...... userService.queryUser(id); //报错:类型转换异常 ...... } |
2. 问题重现与初步分析
重现该问题非常简单:
1.正常启动网关平台,进行服务调用,Dubbo服务一切正常;
2.修改UserPo代码实现,将修改后的代码提交至网关平台进行热加载,不重启网关平台,再次进行服务调用则报错。
初步分析,应该是由于网关平台通过自己实现的内存类加载器(MemoryClassLoaderextends URLClassLoader),提供了Java代码上传之后动态更新、热加载的能力;而在类重新加载之后,存在两个名称完成相同的类。
3. 问题定位——Hession序列化缓存
由于是在Dubbo服务调用返回结果报类型转换异常,怀疑是Dubbo收到响应报文之后进行反序列化,反序列化时使用的Class还是热加载之前的Class。
默认情况下,Dubbo使用的序列化类为
“com.alibaba.com.caucho.hessian.io.SerializerFactory”。通过检查该类代码,发现重要线索,在处理返回对象序列化的getDeserializer()方法中,通过“_cachedTypeDeserializerMap”属性缓存了已经解析过的类,而类的加载通过累加载器属性“_loader”进行记载。
public class SerializerFactoryextendsAbstractSerializerFactory{ private ClassLoader_loader; private HashMap_cachedTypeDeserializerMap; publicDeserializer getDeserializer(String type) throwsHessianProtocolException{ if (_cachedTypeDeserializerMap !=null) { try{ } } |
通过Debug检查动态更新前后的“_loader”,“_cachedTypeDeserializerMap”属性,发现更新前后没有变化,所以无法加载新的类。(使用的ClassLoader为@12068)
更新后的动态ClassLoader已经是“@12125”,与序列化工厂类“com.alibaba.com.caucho.hessian.io.SerializerFactory”的类加载器不同。
4. 问题解决
由于时间紧迫,采取了最快速的解决办法。在路由规则类动态加载完成之后,更新“com.alibaba.com.caucho.hessian.io.SerializerFactory”对象的“_loader”,“_cachedTypeDeserializerMap”属性值。
由于“com.alibaba.com.caucho.hessian.io.SerializerFactory”的属性都为private私有属性,只能通过反射强制进行赋值更新。
} |
修改之后再进行测试,问题修复。
新代码热加载前:
新代码热加载后,dubbo服务调用时用的类加载器与dubbo反序列化时用的类加载器相同: