用 Rust 写 Protobuf 扩展

Posted CITAHub 开发者社区

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了用 Rust 写 Protobuf 扩展相关的知识,希望对你有一定的参考价值。

本文是《Rust唠嗑室》第 17 期文字稿件。作为首届 Rust Aisa Conf 的主持人,目前,志伟正忙于研究云计算与区块链的融合。志伟认为云原生和联盟链最终是要走到一起的。对联盟链来说,支持 k8s 环境可方便复用云原生组件。对云原生来说,区块链提供了去中心化的能力,可以解决边缘计算等场景的问题。为此,志伟提出了云原生区块链框架 CITA-Cloud ,技术上不需要开发太多代码,通过组合云原生社区的已有组件,组装出一条联盟链。


欢迎给国产自主云原生区块链 CITA-Cloud 点赞:

https://github.com/cita-cloud/cita_cloud_proto


在中,志伟希望可以让用户自己来定义协议的组合以及实现方式,从而定制出更加贴近实际业务场景的区块链。具体而言,用户可以自定义核心的交易和区块等数据结构,并通过 Protobuf 扩展增加一些标注,从而方便、自动生成相关代码。在本文中,志伟将着重讲解如何用 Rust 写 Protobuf 扩展。


Protocol Buffers (简称 Protobuf ) ,是 Google 出品的序列化框架,与开发语言无关,和平台无关。具有体积小,速度快,扩展性好,与 gRPC 搭配好,支持的语言多等特点,是目前应用最广泛的序列化框架。


使用场景一般是在微服务架构中,用来定义微服务之间的 gRPC 接口,以及相关的参数/返回值等数据结构的定义。通过官方的编译器  protoc 以及相应的插件可以方便的生成不同语言的实现代码。这样不同的微服务可以使用不同的开发语言,同时还能顺利进行交互。


CITA-Cloud 中的 Protobuf


CITA-Cloud 采用了微服务架构[1],因此也采用了 Protobuf 和 gRPC 的组合。

但是因为 Protobuf 语言无关的特性和广泛的应用,使得其具有抽象和通用的特点。因此也可以把 Protobuf 当作一种建模语言来使用,参见文章[2]


CITA-Cloud 目前是在协议[3]中直接把交易和区块等数据结构固定下来的。但是最近的思考发现,其中的很多字段都是为了实现某种应用层面的协议而存在的。比如交易中的 nonce 字段就是为了实现应用层面的去重协议。


因此,后续计划提供一个框架,方便用户自定义交易和区块等核心数据结构,以及相关的处理函数。但是 Protobuf 通常只能生成数据结构,以及相关的 get/set 等模式比较固定的代码,如果要生成复杂的成员函数,就需要一些扩展能力。


Protobuf扩展


Protobuf 的扩展能力可以分为两种:


  • Protobuf 本身的扩展

  • Protobuf 插件


Protobuf 其实是个标准的编译器架构。我们可以把 .proto 文件视作源码,官方的 protoc 编译器可以对应到编译器前端。protoc 接收一个或者一批 .proto 文件作为输入,解析之后输出一种中间描述格式,对应编译器中的 IR 。


但是有意思的是,这种中间描述格式是二进制的,其结构依旧由 Protobuf 本身描述。详细可以参见descriptor.proto[4]


Protobuf 插件可以对应到编译器后端,接收中间描述格式,解析其中的信息,据此生成具体语言的代码。


这里其实有个非常有意思的问题。插件在解析中间描述格式的数据时,因为这种格式是由 descriptor.proto 描述的,所以得先有个插件能把 descriptor.proto 生成开发插件所使用的开发语言的代码。


上面的话有点绕,举个具体的例子。比如我想用 Rust 实现一个插件,假如目前还没有 Protobuf 相关的 Rust 库,那就没办法用 Rust 代码来解析 descriptor.proto 对应的中间描述格式的数据,也就没法实现插件了。


这个问题其实就对应编译器里的自举问题。比如,想用 Rust 来写 Rust 编译器,那么一开始就是个死结了。解决办法也很简单,最开始的 Rust 编译器是用 Ocaml 实现的,然后就可以用 Rust 来写 Rust 编译器,实现编译器的 Rust 代码用前面 Ocaml 实现的版本去编译就可以解决自举问题了。


Protobuf 这里也是同样的,官方提供了 Java/Go/C++/Python 等版本的实现,可以先用这些语言来过渡。另外一种扩展方式是 Protobuf 本身提供了语法上的扩展机制[5]。这个功能可以对应到编程语言提供的宏等元编程功能。


Protobuf 这个扩展能力有点类似 AOP[6],可以方便的在已经定义的 Message 中增加一些成员。


更有意思的是,前面提到过,所有的 .proto 文件,经过 protoc 之后,会被转换成由 descriptor.proto 对应的中间描述格式。而 descriptor.proto 中的 Message 也同样支持上述扩展功能,因此可以实现一种类似全局 AOP 的功能。通过扩展 descriptor.proto 中的 Message ,可以实现给所有的 Message 都加一个 option 这样的操作。


Rust中相关的库


dropbox 实现了一个 Protobuf 库 pb-jelly[7],它就是用 Python 来实现生成 Rust 代码部分的功能。具体实现其实比较简单,就是在拼 Rust 代码字符串。


rust-protobuf[8] 是一个实现比较完整的 Protobuf 库,支持 gRPC 和相关的扩展能力。其中实现分为两部分,生成数据结构 Rust 代码的插件和生成 gRPC 相关代码的插件。具体实现封装的稍微好了一点,但是基本上还是在拼 Rust 代码字符串。


prost[9] 是一个比较新的 Protobuf 库实现。功能上有点欠缺,不支持扩展。库本身只支持生成数据结构的 Rust 代码。生成 gRPC 相关代码的功能在 tonic-build里[10],这个有点奇怪。


但是 prost 采用了很多新的技术。前面提到,插件只会生成数据结构相关的 get/set 等模式比较固定的代码, prost 实现了一个 derive 来自动给数据结构增加这些成员函数,这样生成的 Rust 代码就大大简化了,参见例子[11]


这也跟编译器架构能对应上:一个选择是把编译器后端做的很复杂,直接生成所有的代码,运行时比较薄;另外一个选择是编译器后端做的很简单,生成的代码也简单,但是运行时比较厚重。


另外 gRPC 相关的代码比较复杂, tonic-build 在生成的时候用了 quote 库[12],提供类似 Rust 代码语法树上的 sprintf 方法的功能,不管是便利性还是代码的可读性都比之前两个库好很多。


后续计划


后续计划使用 Protobuf 及其扩展能力,实现一个框架,不但用来描述交易和区块等核心数据结构,也以一种可配置的方式生成一些比较复杂的相关代码。


最重要的第一步就是要能解析出 Protobuf 扩展相关的信息,因为正常的 .proto 文件只能用于描述数据结构,扩展的 option 是唯一可以赋值的地方。目前实现了一个 proto_desc_printer[13],可以解析中间描述格式,特别是其中的扩展信息。


后续可以在这个基础上去做代码生成部分的工作,这里可以从 prost 吸取很多好的经验。


参考资料


[视频链接]用 Rus t写 Protobuf 扩展

https://www.bilibili.com/video/BV1Ff4y1k7Bo


[1] https://cita-cloud-docs.readthedocs.io/zh_CN/latest/blockchain.html

[2] https://zhuanlan.zhihu.com/p/162839054

[3] https://github.com/cita-cloud/cita_cloud_proto

[4]https://github.com/protocolbuffers/protobuf/blob/master/src/google/protobuf/descriptor.proto

[5] https://developers.google.com/protocol-buffers/docs/proto#extensions

[6] https://www.liaoxuefeng.com/wiki/1252599548343744/1266265125480448

[7] https://github.com/dropbox/pb-jelly

[8] https://github.com/stepancheg/rust-protobuf

[9] https://github.com/danburkert/prost

[10] https://github.com/hyperium/tonic

[11] https://github.com/cita-cloud/cita_cloud_proto/blob/master/src/common.rs

[12] https://github.com/dtolnay/quote

[13] https://github.com/rink1969/proto_desc_printer



-End-

-- 国产自主云原生区块链 CITA-Cloud --


以技术为先驱,100% 自研底层,国产自主云原生区块链 CITA-Cloud 是一个以区块链技术为基础,融合云原生技术的柔性集成开放平台。平台定位于可信连接不同的业务系统,包括传统应用,行业应用,云和物联网设备。 作为一个完整的解决方案,CITA-Cloud 系统架构除了区块链底层,还包括中间件层面的各种服务,以及上层的数字资产管理层。支撑企业用户在各种不同的基础设施上直接构建各种行业解决方案的能力。 


技术文档:https://cita-cloud-docs.readthedocs.io

项目官网:www.citahub.com

交流论坛:https://talk.citahub.com


为国产自主云原生区块链 CITA-Cloud 点赞!

以上是关于用 Rust 写 Protobuf 扩展的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 protobuf-net 扩展?

项目中使用protobuf

Protobuf 语言指南(proto3)

Protobuf语言指南——.proto文件语法详解

golang protobuf unknown字段透传

多个原型中的 FileOptions 扩展