• 线上动态解析protobuf文件,实现动态热更新


    有个需求需要能动态的解析线上的proto数据,因为是对外的应用,不能随时重启,所以需要支持新的数据格式

    1、常规使用protobuf

    《游戏系统设计十三》搞清楚游戏通信协议之protobuf的方方面面_游戏通讯协议-CSDN博客

    之前做过一篇文章,常规的使用细节,总结流程如下

    2、需求

    • 实现协议文件的上传
    • 实现proto文件的运行时解析
    • 实现数据的接入

    文件上传通过springboot的请求支持

    数据在emqx上,通过订阅新的topic可以获得数据

    proto文件的运行时解析从来没做过,也是这次的难点

    3、方案分析

    先看下整个Message的数据结构

    方案1:通过静态编译的方式,然后jvm 虚拟机加载生成的class文件,进行解析

    方案2:通过获取proto的desc描述文件,获取proto的元数据,可以动态更新。

    方案对比:

    方案1更接近原生的使用方式,不利的方式在于jvm的热更新存在的问题

    方案2可以自己管理,非常规方式,主要是灵活

    对比下来,方案2效率略低,更灵活,能更好的满足需求

    4、方案实现

    4.1、desc文件介绍

    Descriptor: 对一个message定义的描述,它包含该message定义的名字、所有字段、内嵌message、内嵌enum、关联的FileDescriptor等。可以使用字段名或字段号查找FieldDescriptor。

    Descriptor:简单理解为元数据

    通过编辑器打开的desc文件,整个文件很小,有一些特殊的字符

    相当于将proto进行了格式转换,可以在运行时进行使用

    4.2、创建desc描述文件

    1. public static String createDesc(String protoFile) throws IOException, InterruptedException {
    2. Path path = Paths.get(protoFile);
    3. String dirName = path.getParent().toString();
    4. String fileName = path.getFileName().toString();
    5. String fileNameNoExt = fileName.split("\\.")[0];
    6. String descriptorFile = dirName + File.separator + fileNameNoExt + ".description";
    7. // 生成descriptor文件
    8. String protocCMD = String.format("protoc --descriptor_set_out=%s %s --include_imports --proto_path %s", descriptorFile, protoFile, dirName);
    9. // protoc --descriptor_set_out=E:\work\bsm.description E:\\work\bsm.proto --include_imports --proto_path E:\work
    10. log.error(protocCMD);
    11. Process process = Runtime.getRuntime().exec(protocCMD);
    12. process.waitFor();
    13. int exitValue = process.exitValue();
    14. if (exitValue != 0) {
    15. log.error("protoc execute failed");
    16. }
    17. return descriptorFile;
    18. }

    这里很简单通过调用protoc文件生成描述文件

    descriptorFile:生成的文件名

    protoFile:需要生成描述文件的proto

    dirName:include文件所在目录

    4.3、获取builder

    ProtoBuf提供了动态解析机制来解决这个问题,它要求提供二进制内容的基础上,再提供对应类的Descriptor对象,在解析时通过DynamicMessage类的成员方法来获得对象结果。

    1. public DynamicMessage.Builder getBuilder(String descriptorFile, String targetClassName) throws Descriptors.DescriptorValidationException, IOException {
    2. Descriptors.Descriptor pbDescritpor = null;
    3. DescriptorProtos.FileDescriptorSet descriptorSet = DescriptorProtos.FileDescriptorSet.parseFrom(new FileInputStream(descriptorFile));
    4. List<Descriptors.FileDescriptor> dependencyFileDescriptorList = new ArrayList<>();
    5. for (int i = 0; i < descriptorSet.getFileCount() - 1; i++) {
    6. dependencyFileDescriptorList.add(Descriptors.FileDescriptor.buildFrom(descriptorSet.getFile(i), dependencyFileDescriptorList.toArray(new Descriptors.FileDescriptor[i])));
    7. }
    8. for (DescriptorProtos.FileDescriptorProto fdp : descriptorSet.getFileList()) {
    9. Descriptors.FileDescriptor fileDescriptor = Descriptors.FileDescriptor.buildFrom(fdp, dependencyFileDescriptorList.toArray(new Descriptors.FileDescriptor[0]));
    10. for (Descriptors.Descriptor descriptor : fileDescriptor.getMessageTypes()) {
    11. String className = fdp.getOptions().getJavaPackage() + "."
    12. + fdp.getOptions().getJavaOuterClassname() + "$"
    13. + descriptor.getName();
    14. log.error(descriptor.getFullName() + " -> " + className);
    15. if (descriptor.getName().equals(targetClassName)) {
    16. log.error("descriptorFile {} targetClassName {} descriptor found", descriptorFile, targetClassName);
    17. pbDescritpor = descriptor;
    18. break;
    19. }
    20. }
    21. if (pbDescritpor != null) {
    22. break;
    23. }
    24. }
    25. if (pbDescritpor == null) {
    26. log.error("descriptorFile {} targetClassName {} No matched descriptor", descriptorFile, targetClassName);
    27. return null;
    28. }
    29. return DynamicMessage.newBuilder(pbDescritpor);
    30. }

    这里解释下参数,

    targetClass:因为proto中可能定义多个Message,这里输入你要转换的那个Message就好

    descriptorFile:上一步生成为文件地址

    4.4、使用

    1. msgBuilder = getBuilder(descriptorFilePath, targetClass);
    2. DynamicMessage pbMessage = msgBuilder.mergeFrom(data).build();
    3. String j = JsonFormat.printer().print(pbMessage);

    这里解析数据,并且转换为json

    5、总结

    这种方式非常规方式,作为动态解析还行,如果能使用静态文件最好使用静态文件

  • 相关阅读:
    Virtink:更轻量的 Kubernetes 原生虚拟化管理引擎
    TCP连接的相关参数
    opencv创建窗口—cv.namedWindow()
    我在自己电脑上固定了随机种子,结果可以复现,但是在云服务器上 不可复现 谁可以帮我看看?有偿
    基于springboot实现“漫画之家”系统项目【项目源码+论文说明】
    数据结构:九种内部排序(动图+完整代码)
    全智V5+AXP233电源管理芯片调试
    Day693.Tomcat如何实现Servlet规范 -深入拆解 Tomcat & Jetty
    allatori8.0文档翻译-第十四步:Eclipse IDE插件
    学习计划
  • 原文地址:https://blog.csdn.net/perfect2011/article/details/138181567