dubbo缺省协议父类和子类拥有相同属性导致的反序列化为null问题排查

       注意:文中涉及的的dubbo版本为公司内部定制化版本。

       今天在工作中遇到一个问题:由于自己的疏忽,定义dubbo接口入参时,WareQueryPageDTO继承了一个父类BizCodeBaseDTO,但是有个属性String bizCode,父类和子类中都存在。本地单元测试,接口都能根据bizCode值查询出对应的数据,但是dubbo Consumer端查出的数据一直是错误的。

       检查日志发现,接口消费端明明传入了bizCode值,但是却没有解析出来:

采用的是dubbo缺省协议,序列化方式是Hessian 二进制序列化,在反序列化时会出现父类的同名属性覆盖子类的同名属性问题,所以就变成null,解析不到了。我们使用的是dubbo 2.8.4版本【公司内部定制版本】,这里序列化和反序列化涉及到两个类,在dubbo-serialization-api 包中:

序列化类:com.alibaba.com.caucho.hessian.io.JavaSerializer

反序列化类:com.alibaba.com.caucho.hessian.io.JavaDeserializer

先看一下JavaSerializer序列化方式:

 public JavaSerializer(Class cl, ClassLoader loader) {
        this.introspectWriteReplace(cl, loader);
        if (this._writeReplace != null) {
            this._writeReplace.setAccessible(true);
        }

        List primitiveFields = new ArrayList();

        ArrayList compoundFields;
        int i;
        for(compoundFields = new ArrayList(); cl != null; cl = cl.getSuperclass()) {
            Field[] fields = cl.getDeclaredFields();

            for(i = 0; i < fields.length; ++i) {
                Field field = fields[i];
                if (!Modifier.isTransient(field.getModifiers()) && !Modifier.isStatic(field.getModifiers())) {
                    field.setAccessible(true);
                    if (!field.getType().isPrimitive() && (!field.getType().getName().startsWith("java.lang.") || field.getType().equals(Object.class))) {
                        compoundFields.add(field);
                    } else {
                        primitiveFields.add(field);
                    }
                }
            }
        }

        List fields = new ArrayList();
        fields.addAll(primitiveFields);
        fields.addAll(compoundFields);
        Collections.reverse(fields);
        this._fields = new Field[fields.size()];
        fields.toArray(this._fields);
        this._fieldSerializers = new JavaSerializer.FieldSerializer[this._fields.length];

        for(i = 0; i < this._fields.length; ++i) {
            this._fieldSerializers[i] = getFieldSerializer(this._fields[i].getType());
        }

    }

在循环中先存储子类的属性(基本属性primitiveFields + 引用属性compoundFields),后存储父类的属性,但是

Collections.reverse(fields);

这行代码在一些低版本中是不存在的,通过逆序操作,其实父类的属性在前面,子类属性在后面了。我本地debug结果也是这样:

本例中:是WaresQueryPageDTO extends BizCodeBaseDTO,相同的属性是String bizCode。

再看一下JavaDeserializer反序列化方式:

//组装HashMap
 protected HashMap getFieldMap(Class cl) {
        HashMap fieldMap;
        for(fieldMap = new HashMap(); cl != null; cl = cl.getSuperclass()) {
            Field[] fields = cl.getDeclaredFields();

            for(int i = 0; i < fields.length; ++i) {
                Field field = fields[i];
                if (!Modifier.isTransient(field.getModifiers()) && !Modifier.isStatic(field.getModifiers()) && fieldMap.get(field.getName()) == null) {
                    try {
                        field.setAccessible(true);
                    } catch (Throwable var8) {
                        var8.printStackTrace();
                    }

                    Class type = field.getType();
                    Object deser;
                    if (String.class.equals(type)) {
                        deser = new JavaDeserializer.StringFieldDeserializer(field);
                    } else if (Byte.TYPE.equals(type)) {
                        deser = new JavaDeserializer.ByteFieldDeserializer(field);
                    } else if (Short.TYPE.equals(type)) {
                        deser = new JavaDeserializer.ShortFieldDeserializer(field);
                    } else if (Integer.TYPE.equals(type)) {
                        deser = new JavaDeserializer.IntFieldDeserializer(field);
                    } else if (Long.TYPE.equals(type)) {
                        deser = new JavaDeserializer.LongFieldDeserializer(field);
                    } else if (Float.TYPE.equals(type)) {
                        deser = new JavaDeserializer.FloatFieldDeserializer(field);
                    } else if (Double.TYPE.equals(type)) {
                        deser = new JavaDeserializer.DoubleFieldDeserializer(field);
                    } else if (Boolean.TYPE.equals(type)) {
                        deser = new JavaDeserializer.BooleanFieldDeserializer(field);
                    } else if (Date.class.equals(type)) {
                        deser = new JavaDeserializer.SqlDateFieldDeserializer(field);
                    } else if (Timestamp.class.equals(type)) {
                        deser = new JavaDeserializer.SqlTimestampFieldDeserializer(field);
                    } else if (Time.class.equals(type)) {
                        deser = new JavaDeserializer.SqlTimeFieldDeserializer(field);
                    } else if (Map.class.equals(type) && field.getGenericType() != field.getType()) {
                        deser = new JavaDeserializer.ObjectMapFieldDeserializer(field);
                    } else if (List.class.equals(type) && field.getGenericType() != field.getType()) {
                        deser = new JavaDeserializer.ObjectListFieldDeserializer(field);
                    } else {
                        deser = new JavaDeserializer.ObjectFieldDeserializer(field);
                    }

                    fieldMap.put(field.getName(), deser);
                }
            }
        }

        return fieldMap;
    }

//反序列化
 public Object readMap(AbstractHessianInput in, Object obj) throws IOException {
        try {
            int ref = in.addRef(obj);

            Object resolve;
            while(!in.isEnd()) {
                resolve = in.readObject();
                JavaDeserializer.FieldDeserializer deser = (JavaDeserializer.FieldDeserializer)this._fieldMap.get(resolve);
                if (deser != null) {
                    deser.deserialize(in, obj);
                } else {
                    in.readObject();
                }
            }

            in.readMapEnd();
            resolve = this.resolve(obj);
            if (obj != resolve) {
                in.setRef(ref, resolve);
            }

            return resolve;
        } catch (IOException var6) {
            throw var6;
        } catch (Exception var7) {
            throw new IOExceptionWrapper(var7);
        }
    }

是把序列化中的属性List放到HashMap中,HashMap的key用的是字符串类型:field.getName()。如果是低版本的Dubbo框架,没有上面Collections.reverse(fields);这行代码,的确会导致解析失败,因为HashMap的key是唯一的,如果没有这行代码,属性List是子类的在前,父类的在后,这样反序列化时,如例中的bizCode,子类的属性有值,HashMap赋值了,但是后面父类中的bizCode没有值,所以又被赋为null了。

       到这里,我为什么会遇到父类子类bizCode反序列化为null的情况呢?我怀疑是对方Consumer端dubbo服务版本较低,是有问题的版本,可能没有逆序父类子类顺序那行代码导致的。我本地启动一个Provider服务和Consumer服务,发现调用是没有问题的,数据返回正常。翌日,询问了一下接口调用方,反馈他们使用的dubbo版本是2.6.8版本,查了一下2.6.8版本的JavaSerializer序列化方式那一段代码:

public JavaSerializer(Class cl, ClassLoader loader) {
        this.introspectWriteReplace(cl, loader);
        if (this._writeReplace != null) {
            this._writeReplace.setAccessible(true);
        }

        ArrayList primitiveFields = new ArrayList();

        ArrayList compoundFields;
        int i;
        for(compoundFields = new ArrayList(); cl != null; cl = cl.getSuperclass()) {
            Field[] fields = cl.getDeclaredFields();

            for(i = 0; i < fields.length; ++i) {
                Field field = fields[i];
                if (!Modifier.isTransient(field.getModifiers()) && !Modifier.isStatic(field.getModifiers())) {
                    field.setAccessible(true);
                    if (!field.getType().isPrimitive() && (!field.getType().getName().startsWith("java.lang.") || field.getType().equals(Object.class))) {
                        compoundFields.add(field);
                    } else {
                        primitiveFields.add(field);
                    }
                }
            }
        }
        /**注意:没有属性逆转那行代码*/
        ArrayList fields = new ArrayList();
        fields.addAll(primitiveFields);
        fields.addAll(compoundFields);
        this._fields = new Field[fields.size()];
        fields.toArray(this._fields);
        this._fieldSerializers = new JavaSerializer.FieldSerializer[this._fields.length];

        for(i = 0; i < this._fields.length; ++i) {
            this._fieldSerializers[i] = getFieldSerializer(this._fields[i].getType());
        }

    }

可以看出,在2.6.8版本里,是没有属性逆转那行代码的,这会导致子类属性在前,父类属性在后,反序列化时子类的属性值就被父类的相同属性覆盖了,这也解释了为什么我本地启动两个服务调用没问题,第三方调用有问题,因为是第三方dubbo版本略低的问题。

  • 5
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Dubbo连接超时问题通常是由于网络延迟、服务提供者负载过高或者配置错误等多种原因引起的。下面是排查Dubbo连接超时问题的一些常见方法和建议: 1. 检查网络延迟:首先,可以尝试通过ping命令来检查与服务提供者之间的网络延迟情况。如果延迟较高,可能需要优化网络环境或者切换到更稳定的网络连接。 2. 服务提供者负载过高:可以查看服务提供者的系统资源使用情况,例如 CPU、内存、磁盘等是否过载。如果负载过高,可以优化代码、增加服务器资源或者增加服务实例数等来提高性能。 3. 超时配置错误:可以检查Dubbo的超时配置是否正确。例如,可以确认是否设置了正确的连接超时时间、读写超时时间等,以及是否合理地设置了重试次数等参数。 4. 服务提供者响应时间过长:可以对服务提供者进行性能分析,找出响应时间较长的接口或者方法,并优化其实现。如果有必要,可以采用异步调用方式来提高吞吐量和响应速度。 5. 检查服务调用链路:可以通过监控工具或者日志来查看服务调用链路,找出是否存在调用关系错乱、环路或者循环依赖等问题。这些问题可能导致连接超时或者请求被阻塞。 6. 调整Dubbo配置参数:可以尝试调整Dubbo的相关配置参数,如线程池大小、队列大小、IO线程数等,以适应当前的应用场景。 总之,解决Dubbo连接超时问题的关键是要深入分析问题背后的原因,并针对性地采取相应的优化措施。在排查问题过程中,可以结合相关的监控工具、日志和性能测试工具来帮助定位和解决问题

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值