vlambda博客
学习文章列表

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) {
synchronized(_cachedTypeDeserializerMap) {
deserializer = (Deserializer)
_cachedTypeDeserializerMap.get(type);
}
if(deserializer !=null)
returndeserializer;
}
try{
Class cl = Class.forName(type,
false,_loader);
deserializer = getDeserializer(cl);
}
catch (Exception e) {
log.warning("Hessian/Burlap: '"+ type +"' is an unknown class in "+_loader +":\n"+ e);
log.log(Level.FINER, e.toString(), e);
   }
 }
}

通过Debug检查动态更新前后的“_loader”,_cachedTypeDeserializerMap”属性,发现更新前后没有变化,所以无法加载新的类。(使用的ClassLoader为@12068)

 

更新后的动态ClassLoader已经是“@12125”,与序列化工厂类“com.alibaba.com.caucho.hessian.io.SerializerFactory”的类加载器不同。

Dubbo服务热加载踩坑记


4.  问题解决

由于时间紧迫,采取了最快速的解决办法。在路由规则类动态加载完成之后,更新“com.alibaba.com.caucho.hessian.io.SerializerFactory”对象的“_loader”,“_cachedTypeDeserializerMap”属性值。

由于“com.alibaba.com.caucho.hessian.io.SerializerFactory”的属性都为private私有属性,只能通过反射强制进行赋值更新。

private static void updateDubboSerializerFactory

(MemoryClassLoader classLoader) {

//1.更新的反序列化
SerializerFactory serializerFactory = Hessian2SerializerFactory.SERIALIZER_FACTORY;
        ReflectionUtils.findField(SerializerFactory.
class,"_cachedTypeDeserializerMap").setAccessible(true);
        HashMap map = (HashMap) ReflectionUtils.findField(SerializerFactory.
class,"_cachedTypeDeserializerMap").get(serializerFactory);
if(map != null) {
            map.clear();
        }
//2.更新使用的ClassLoader
ReflectionUtils.findField(SerializerFactory.class,"_loader").setAccessible(true);
ReflectionUtils.setField(ReflectionUtils.findField(SerializerFactory.
class,"_loader"), serializerFactory, classLoader);
}

修改之后再进行测试,问题修复。

新代码热加载前:

Dubbo服务热加载踩坑记

新代码热加载后,dubbo服务调用时用的类加载器与dubbo反序列化时用的类加载器相同:

Dubbo服务热加载踩坑记