两个Dubbo踩过的坑

叶斌

一:Dubbo异步调用链路上传递
  自己服务内调用的一个返回值是int类型的服务,期望result=1,原本一切正常,可是再一次功能迭代开发后突然结果时而是result=0时而是result=1,而且大概率情况是result=0,导致自己的服务出现问题。
  因为碰巧result=0也是一个业务上可能的数据,刚开始排查问题怀疑是服务提供方的问题。来回排查后确定服务方没有问题,浪费大量人力和时间后,发现调用自己服务的上游调用方调用方式刚刚改造成了异步。随后分别写了单测模拟Dubbo同步和异步方式调用自己的问题服务,果然异步调用出现了问题,怀疑是Dubbo异步调用引起的问题,网上一番查询后确定就是dubbo异步调用的坑。
  Dubbo异步调用的属性在RpcContext里,会在Dubbo调用链路上传递一次:原始调用方A异步调用服务B,服务B在内部同步调用服务C,会变成B异步调用C。
  修正方式增加一个Dubbo filter设置RpcContext异步调用属性为false。

    @Override
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
    // fix: dubbo 消费端异步属性会使服务端dubbo调用链路传递一次
    Map<String, String> context = RpcContext.getContext().getAttachments();
        context.put(Constants.ASYNC_KEY, Boolean.FALSE.toString());
    return invoker.invoke(invocation);

}

二:Dubbo Hessian序列化参数为NULL问题
  一次收到BUG反馈功能不能正常使用,通过查看Dubbo-accesslog找到服务接受参数DTO的目标字段为NULL。翻看代码递交记录发现因为重构参数DTO新继承了父类,目标字段不同于其他字段的唯一可疑点是在父类和子类重复定义了。
  虽然觉得Dubbo不可能会有这样Bug,但是删除子类字段定义后,服务接受参数正常了。Dubbo默认的序列化是hessian,查看了下Dubbo包里的hessian源代码发现:
JavaSerializer在获取fileds是子类数据在前,父类数据,然后再对fileds写值

      public JavaSerializer(Class cl, ClassLoader loader) {
    introspectWriteReplace(cl, loader);

    if (_writeReplace != null)
        _writeReplace.setAccessible(true);

    ArrayList primitiveFields = new ArrayList();
    ArrayList compoundFields = new ArrayList();

    for (; cl != null; cl = cl.getSuperclass()) {
        Field[] fields = cl.getDeclaredFields();
        for (int i = 0; i < fields.length; i++) {
            Field field = fields[i];

            // 省略
        }
    }

    // 省略
}

JavaDeserializer在获取fileds时,采用了Map去重,这是没有问题,但是读取值时,根据serializer的顺序,对于同名字段,子类的该字段值会被赋值两次,总是被父类的值覆盖,导致子类的字段值丢失。

protected HashMap getFieldMap(Class cl) {
    HashMap fieldMap = new HashMap();

    for (; 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()))
                continue;
            else if (fieldMap.get(field.getName()) != null)
                continue;

            // XXX: could parameterize the handler to only deal with public
            try {
                field.setAccessible(true);
            } catch (Throwable e) {
                e.printStackTrace();
            }

            Class type = field.getType();
            FieldDeserializer deser;

            if (String.class.equals(type))
                deser = new StringFieldDeserializer(field);
            else if (byte.class.equals(type)) {
                deser = new ByteFieldDeserializer(field);
            } else if (short.class.equals(type)) {
                deser = new ShortFieldDeserializer(field);
            } else if (int.class.equals(type)) {
                deser = new IntFieldDeserializer(field);
            } else if (long.class.equals(type)) {
                deser = new LongFieldDeserializer(field);
            } else if (float.class.equals(type)) {
                deser = new FloatFieldDeserializer(field);
            } else if (double.class.equals(type)) {
                deser = new DoubleFieldDeserializer(field);
            } else if (boolean.class.equals(type)) {
                deser = new BooleanFieldDeserializer(field);
            } else if (java.sql.Date.class.equals(type)) {
                deser = new SqlDateFieldDeserializer(field);
            } else if (java.sql.Timestamp.class.equals(type)) {
                deser = new SqlTimestampFieldDeserializer(field);
            } else if (java.sql.Time.class.equals(type)) {
                deser = new SqlTimeFieldDeserializer(field);
            } else {
                deser = new ObjectFieldDeserializer(field);
            }

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

    return fieldMap;
}

public Object readMap(AbstractHessianInput in, Object obj) throws IOException {
    try {
        int ref = in.addRef(obj);

        while (!in.isEnd()) {
            Object key = in.readObject();

            FieldDeserializer deser = (FieldDeserializer) _fieldMap.get(key);

            if (deser != null)
                deser.deserialize(in, obj);
            else
                in.readObject();
        }

        in.readMapEnd();

        Object resolve = resolve(obj);

        if (obj != resolve)
            in.setRef(ref, resolve);

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

  所以dubbo在采用hessian序列化时,一定要注意子类和父类不能有同名字段。