[alibaba/arthas]Arthas内部服务异步化,再包装为 gRPC 服务暴露

2024-07-17 277 views
2
  1. 目前Arthas的功能都是基于 命令行 的 Command 思路实现的,大部分交互都是在 terminal 窗口
  2. 这种cli的交互导致Arthas 的功能不能很好的以 API 方式暴露,尽管之前实现过一版 HTTP API 的支持,但并不是很友好 https://arthas.aliyun.com/doc/http-api.html
  3. 基于命令行 Command 实现,导致很多功能被限制了,并且实现比较复杂

抛开之前的实现来思考,Arthas本身内部的功能大致可以分为两类:

  1. 一次性执行,比如 sysenv, jvm 等,这种是比较简单的。
  2. 可交互的长时间执行,比如像 watch/trace

重点是类watch/trace的任务

  1. 执行中可能修改任务的配置,比如 watch 监听的 结果表达式, 条件表达式 ,可以支持动态更新。这样子用户不需要重新执行一次 watch
  2. 增强的类可以持续增加,比如像 trace,可以更容易实现 动态trace 功能 https://arthas.aliyun.com/doc/trace.html#%E5%8A%A8%E6%80%81-trace
  3. 这类任务需要支持 持续接收 输入,也有持续的结果输出。这个和 gRPC 的双向Stream功能很像
  4. 为了简化实现,和支持 gRPC Web(gRPC Web目前只支持 server stream) ,尽量只考虑 gRPC server stream ,不考虑 client stream ,client stream 可能考虑通过传入 jobId 的方式解决
  5. 为了更好的适配,在arthas内部不会直接使用 gRPC 的API,而是提供类似 io.grpc.stub.StreamObserver 的接口,后面再转接到 gRPC 上

下面是一种sysprop 命令的对应 servcie 的定义

message StringKey {
    string key = 1;
}

message StringValue {
    string value = 1;
}

message Properties {
    map<string, string> properties = 1;
}

service SystemProperty {
    rpc get(google.protobuf.Empty) returns (Properties);
    rpc getByKey(StringKey) returns (StringValue);
    rpc update(Properties) returns (Properties);
}
  • watch/trace 这种需要持续交互的命令定义还需要实践尝试。

关联Issue:

回答

3

考虑提供一个展示 java object的 pb 定义,类似com.taobao.arthas.core.view.ObjectView


message JavaField {
  string name = 1;

  oneof value {
    JavaObject objectValue = 2;
    BasicValue basicValue = 3;
    ArrayValue arrayValue = 4;
    NullValue nullValue = 5;
  }
}

message JavaObject {
  string className = 1;
  repeated JavaField fields = 2;
}

message BasicValue {
  oneof value {
    int32 intValue = 1;
    int64 longValue = 2;
    float floatValue = 3;
    double doubleValue = 4;
    bool booleanValue = 5;
    string stringValue = 6;
  }
}

message ArrayElement {
  oneof element {
    BasicValue basicValue = 1;
    JavaObject objectValue = 2;
    ArrayValue arrayValue = 3;
    NullValue nullValue = 4;
  }
}

message ArrayValue {
  string className = 1;
  repeated ArrayElement elements = 2;
}

message NullValue {
  string className = 1;
}

测试代码:

// ComplexObject.java
public class ComplexObject {
    private int id;
    private String name;
    private double value;
    private int[] numbers;
    private NestedObject nestedObject;
    private ComplexObject[] complexArray;
    private int[][] multiDimensionalArray;
    private String[] stringArray;

    public static class NestedObject {
        private int nestedId;
        private String nestedName;
        private boolean flag;

        public int getNestedId() {
            return nestedId;
        }

        public void setNestedId(int nestedId) {
            this.nestedId = nestedId;
        }

        public String getNestedName() {
            return nestedName;
        }

        public void setNestedName(String nestedName) {
            this.nestedName = nestedName;
        }

        public boolean isFlag() {
            return flag;
        }

        public void setFlag(boolean flag) {
            this.flag = flag;
        }

    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public double getValue() {
        return value;
    }

    public void setValue(double value) {
        this.value = value;
    }

    public int[] getNumbers() {
        return numbers;
    }

    public void setNumbers(int[] numbers) {
        this.numbers = numbers;
    }

    public NestedObject getNestedObject() {
        return nestedObject;
    }

    public void setNestedObject(NestedObject nestedObject) {
        this.nestedObject = nestedObject;
    }

    public ComplexObject[] getComplexArray() {
        return complexArray;
    }

    public void setComplexArray(ComplexObject[] complexArray) {
        this.complexArray = complexArray;
    }

    public int[][] getMultiDimensionalArray() {
        return multiDimensionalArray;
    }

    public void setMultiDimensionalArray(int[][] multiDimensionalArray) {
        this.multiDimensionalArray = multiDimensionalArray;
    }

    public String[] getStringArray() {
        return stringArray;
    }

    public void setStringArray(String[] stringArray) {
        this.stringArray = stringArray;
    }

}

构建对象,解析输出:

    public static void main(String[] args) {
        ComplexObject ccc = ccc();
        JavaObject javaObject = toJavaObject(ccc);

        System.err.println(javaObject);
    }

    public static ComplexObject ccc() {
        // 创建一个 ComplexObject 对象
        ComplexObject complexObject = new ComplexObject();

        // 设置基本类型的值
        complexObject.setId(1);
        complexObject.setName("Complex Object");
        complexObject.setValue(3.14);

        // 设置基本类型的数组
        int[] numbers = { 1, 2, 3, 4, 5 };
        complexObject.setNumbers(numbers);

        // 创建并设置嵌套对象
        ComplexObject.NestedObject nestedObject = new ComplexObject.NestedObject();
        nestedObject.setNestedId(10);
        nestedObject.setNestedName("Nested Object");
        nestedObject.setFlag(true);
        complexObject.setNestedObject(nestedObject);

        // 创建并设置复杂对象数组
        ComplexObject[] complexArray = new ComplexObject[2];

        ComplexObject complexObject1 = new ComplexObject();
        complexObject1.setId(2);
        complexObject1.setName("Complex Object 1");
        complexObject1.setValue(2.71);

        ComplexObject complexObject2 = new ComplexObject();
        complexObject2.setId(3);
        complexObject2.setName("Complex Object 2");
        complexObject2.setValue(1.618);

        complexArray[0] = complexObject1;
        complexArray[1] = complexObject2;

        complexObject.setComplexArray(complexArray);

        // 创建并设置多维数组
        int[][] multiDimensionalArray = { { 1, 2, 3 }, { 4, 5, 6 } };
        complexObject.setMultiDimensionalArray(multiDimensionalArray);

        // 设置数组中的基本元素数组
        String[] stringArray = { "Hello", "World" };
        complexObject.setStringArray(stringArray);

        // 输出 ComplexObject 对象的信息
        System.out.println(complexObject);

        return complexObject;
    }

    public static JavaObject toJavaObject(Object obj) {
        JavaObject.Builder objectBuilder = JavaObject.newBuilder();
        objectBuilder.setClassName(obj.getClass().getName());

        Field[] fields = obj.getClass().getDeclaredFields();
        List<JavaField> javaFields = new ArrayList<>();

        for (Field field : fields) {
            field.setAccessible(true);
            JavaField.Builder fileBuilder = JavaField.newBuilder();
            fileBuilder.setName(field.getName());
            try {
                Object fieldValue = field.get(obj);
                Class<?> fieldType = field.getType();
                if(fieldValue == null) {
                    fileBuilder.setNullValue(NullValue.newBuilder().setClassName(fieldType.getName()).build());
                }
                else if (fieldType.isArray()) {
                    ArrayValue arrayValue = toArrayValue(fieldValue);
                    fileBuilder.setArrayValue(arrayValue);
                } else if (fieldValue != null) {
                    if (fieldType.isArray()) {
                        JavaObject nestedArrayObject = toJavaObject(fieldValue);
                        fileBuilder.setObjectValue(nestedArrayObject);
                    } else if (fieldType.isPrimitive() || fieldType.equals(String.class)) {
                        BasicValue basicValue = createBasicValue(fieldValue);
                        fileBuilder.setBasicValue(basicValue);
                    } else {
                        JavaObject nestedArrayObject = toJavaObject(fieldValue);
                        fileBuilder.setObjectValue(nestedArrayObject);
                    }
                }
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }

            javaFields.add(fileBuilder.build());
        }

        objectBuilder.addAllFields(javaFields);
        return objectBuilder.build();
    }

    private static ArrayValue toArrayValue(Object a) {
        ArrayValue.Builder arrayBuilder = ArrayValue.newBuilder();

        Class<?> type = a.getClass().getComponentType();

        arrayBuilder.setClassName(type.getName());

        for (int i = 0; i < Array.getLength(a); i++) {
            Object arrayElement = Array.get(a, i);
            if (arrayElement != null) {
                if (type.isArray()) {
                    ArrayValue arrayValue = toArrayValue(arrayElement);
                    arrayBuilder.addElements(ArrayElement.newBuilder().setArrayValue(arrayValue));
                } else if (type.isPrimitive() || type.equals(String.class)) {
                    BasicValue basicValue = createBasicValue(arrayElement);
                    arrayBuilder.addElements(ArrayElement.newBuilder().setBasicValue(basicValue));
                } else {
                    JavaObject nestedArrayObject = toJavaObject(arrayElement);
                    arrayBuilder.addElements(ArrayElement.newBuilder().setObjectValue(nestedArrayObject));
                }
            }else {
                arrayBuilder.addElements(ArrayElement.newBuilder().setNullValue(NullValue.newBuilder().setClassName(type.getName()).build()));
            }
        }
        return arrayBuilder.build();
    }

    private static BasicValue createBasicValue(Object value) {
        Builder builder = BasicValue.newBuilder();
        if (value instanceof Integer) {
            builder.setIntValue((Integer) value);
        } else if (value instanceof Long) {
            builder.setLongValue((Long) value);
        } else if (value instanceof Float) {
            builder.setFloatValue((Float) value);
        } else if (value instanceof Double) {
            builder.setDoubleValue((Double) value);
        } else if (value instanceof Boolean) {
            builder.setBooleanValue((Boolean) value);
        } else if (value instanceof String) {
            builder.setStringValue((String) value);
        }
        return builder.build();
    }

上面的代码还有问题,没有处理循环引用。

  • 或许应该考虑为每个对象增加 id , refId 信息,这样子避免循环引用。
  • 或者考虑在展开对象时,考虑限制展开层数,比如展开到3层之后,就不再展开。
4

貌似还是需要展开层数这个配置。增加一个 UnexpandedObject 的定义:

message JavaField {
  string name = 1;

 oneof value {
   JavaObject objectValue = 2;
   BasicValue basicValue = 3; 
   ArrayValue arrayValue = 4;
   NullValue nullValue = 5;
   UnexpandedObject unexpandedObject = 6;
 }
}

message JavaObject {
  string className = 1;
  repeated JavaField fields = 2;
}

message BasicValue {
  oneof value {
    int32 intValue = 2;
    int64 longValue = 3;
    float floatValue = 4;
    double doubleValue = 5;
    bool booleanValue = 6;
    string stringValue = 7;
  }
}

message ArrayElement {
  oneof element {
    BasicValue basicValue = 2;
    JavaObject objectValue = 3;
    ArrayValue arrayValue = 5;
    NullValue nullValue = 6;
    UnexpandedObject unexpandedObject = 7;
  }
}

message ArrayValue {
  string className = 1;
  repeated ArrayElement elements = 2;
}

message NullValue {
  string className = 1;
}

message UnexpandedObject {
  string className = 1;
}

public class JavaObjectConverter {
    private static final int MAX_DEPTH = 3;

    public static JavaObject toJavaObject(Object obj) {
        return toJavaObject(obj, 0);
    }

    public static JavaObject toJavaObject(Object obj, int depth) {
        if (obj == null || depth >= MAX_DEPTH) {
            return null;
        }

        JavaObject.Builder objectBuilder = JavaObject.newBuilder();
        objectBuilder.setClassName(obj.getClass().getName());

        Field[] fields = obj.getClass().getDeclaredFields();
        List<JavaField> javaFields = new ArrayList<>();

        for (Field field : fields) {
            field.setAccessible(true);
            JavaField.Builder fieldBuilder = JavaField.newBuilder();
            fieldBuilder.setName(field.getName());

            try {
                Object fieldValue = field.get(obj);
                Class<?> fieldType = field.getType();

                if (fieldValue == null) {
                    fieldBuilder.setNullValue(NullValue.newBuilder().setClassName(fieldType.getName()).build());
                } else if (fieldType.isArray()) {
                    ArrayValue arrayValue = toArrayValue(fieldValue, depth + 1);
                    if (arrayValue != null) {
                        fieldBuilder.setArrayValue(arrayValue);
                    } else {
                        fieldBuilder.setUnexpandedObject(
                                UnexpandedObject.newBuilder().setClassName(fieldType.getName()).build());
                    }
                } else if (fieldType.isPrimitive() || fieldType.equals(String.class)) {
                    BasicValue basicValue = createBasicValue(fieldValue);
                    fieldBuilder.setBasicValue(basicValue);
                } else {
                    JavaObject nestedObject = toJavaObject(fieldValue, depth + 1);
                    if (nestedObject != null) {
                        fieldBuilder.setObjectValue(nestedObject);
                    } else {
                        fieldBuilder.setUnexpandedObject(
                                UnexpandedObject.newBuilder().setClassName(fieldType.getName()).build());
                    }
                }
            } catch (IllegalAccessException e) {
                // Handle the exception appropriately
                e.printStackTrace();
            }

            javaFields.add(fieldBuilder.build());
        }

        objectBuilder.addAllFields(javaFields);
        return objectBuilder.build();
    }

    private static ArrayValue toArrayValue(Object array, int depth) {
        if (array == null || depth >= MAX_DEPTH) {
            return null;
        }

        ArrayValue.Builder arrayBuilder = ArrayValue.newBuilder();
        Class<?> componentType = array.getClass().getComponentType();

        arrayBuilder.setClassName(componentType.getName());

        int length = Array.getLength(array);
        for (int i = 0; i < length; i++) {
            Object element = Array.get(array, i);

            if (element != null) {
                if (componentType.isArray()) {
                    ArrayValue nestedArrayValue = toArrayValue(element, depth + 1);
                    if (nestedArrayValue == null) {
                        arrayBuilder.addElements(ArrayElement.newBuilder().setArrayValue(nestedArrayValue));
                    } else {
                        arrayBuilder.addElements(ArrayElement.newBuilder().setUnexpandedObject(
                                UnexpandedObject.newBuilder().setClassName(element.getClass().getName()).build()));
                    }

                } else if (componentType.isPrimitive() || componentType.equals(String.class)) {
                    BasicValue basicValue = createBasicValue(element);
                    arrayBuilder.addElements(ArrayElement.newBuilder().setBasicValue(basicValue));
                } else {
                    JavaObject nestedObject = toJavaObject(element, depth + 1);
                    if (nestedObject != null) {
                        arrayBuilder.addElements(ArrayElement.newBuilder().setObjectValue(nestedObject));
                    } else {
                        arrayBuilder.addElements(ArrayElement.newBuilder().setUnexpandedObject(
                                UnexpandedObject.newBuilder().setClassName(element.getClass().getName()).build()));
                    }

                }
            } else {
                arrayBuilder.addElements(ArrayElement.newBuilder()
                        .setNullValue(NullValue.newBuilder().setClassName(componentType.getName()).build()));
            }
        }

        return arrayBuilder.build();
    }

    private static BasicValue createBasicValue(Object value) {
        BasicValue.Builder builder = BasicValue.newBuilder();

        if (value instanceof Integer) {
            builder.setIntValue((int) value);
        } else if (value instanceof Long) {
            builder.setLongValue((long) value);
        } else if (value instanceof Float) {
            builder.setFloatValue((float) value);
        } else if (value instanceof Double) {
            builder.setDoubleValue((double) value);
        } else if (value instanceof Boolean) {
            builder.setBooleanValue((boolean) value);
        } else if (value instanceof String) {
            builder.setStringValue((String) value);
        }

        return builder.build();
    }
}