我的团队,Mozilla 的 应用程序服务 团队,负责 Firefox 同步、Firefox 账户 和 WebPush.
这些功能目前已在 Firefox 桌面、Android 和 iOS 浏览器中发布。它们很快将在我们的新产品中提供,例如我们即将推出的 Android 浏览器、我们的密码管理器 Lockbox 和 Firefox for Fire TV。
解决过多的目标
到目前为止,出于历史原因,这些功能的实现方式大相径庭。它们与它们所支持的产品紧密耦合,这使得它们难以复用。例如,目前有三个 Firefox 同步客户端:一个在 JavaScript 中,一个在 Java 中,另一个在 Swift 中。
考虑到我们团队的规模,我们很快意识到,我们目前的产品发布方法无法扩展到更多产品,并会导致质量问题,例如跨平台特定实现的错误或功能不完整。大约一年前,我们决定为未来规划。正如你所知,Mozilla 在新的 Rust 编程语言上下了很大的赌注,因此我们自然而然地紧随其后。
使用 Rust 的跨平台策略
我们的新策略如下:我们将构建跨平台组件,使用 Rust 实现我们的核心业务逻辑,并将其封装在一个薄的平台原生层中,例如 Kotlin 用于 Android,Swift 用于 iOS。
最大也是最明显的优势是,我们获得了一个规范的代码库,它使用安全且静态类型的语言编写,可以在每个平台上部署。每次上游业务逻辑更改都会随着版本升级而对我们所有产品生效。
我们如何安全地解决 FFI 挑战
但是,这种新方法面临的挑战之一是在 API 边界安全地传递结构丰富的數據,使其内存安全并与 Rust 的所有权系统良好配合。我们编写了 ffi-support 箱子来帮助解决这个问题,如果你编写执行 FFI (外部函数接口) 任务的代码,你应该认真考虑使用它。我们的初始版本通过将我们的数据结构序列化为 JSON 字符串来实现,并且在少数情况下通过指针返回 C 形结构体。例如,从用户的同步数据中返回书签的程序如下所示
使用 JSON 从 Rust 返回书签数据到 Kotlin(简化)
那么这种方法的问题是什么?
- 性能:JSON 序列化和反序列化速度非常慢,因为性能不是该格式的主要设计目标。最重要的是,在 Java 层会发生额外的字符串复制,因为 Rust 字符串是 UTF-8,而 Java 字符串是 UTF-16 的。在规模上,它会引入明显的开销。
- 复杂性和安全性:每个数据结构都必须从 JSON 字符串中手动解析和反序列化。在 Rust 端对数据结构字段进行修改 **必须** 在 Kotlin 端反映出来,否则很可能发生异常。
- 更糟糕的是,在某些情况下,我们返回的不是 JSON 字符串,而是通过指针返回的 C 形 Rust 结构体:忘记更新结构 Kotlin 子类或 Objective-C 结构体,你就会遇到严重的内存损坏。
我们很快意识到,可能有一种比我们当前解决方案更好、更快的、更安全的方法。
使用 Protocol Buffers v.2 进行数据序列化
谢天谢地,有 许多数据序列化格式 旨在快速完成任务。具有模式语言的格式甚至会为你自动生成数据结构!
经过一番探索,我们最终决定使用 Protocol Buffers 版本 2。
——相对——安全性来自在我们需要关注的语言中自动生成数据结构。只有一个真理源——.proto 模式文件——所有数据类都在构建时从该文件生成,无论是 Rust 还是消费方。
protoc(Protocol Buffers 代码生成器)可以在超过 20 种语言中生成代码!在 Rust 端,我们使用 prost 箱子,它通过利用 Rust 导出宏来输出 非常简洁的结构体。
使用 Protocol Buffers 2 从 Rust 返回书签数据到 Kotlin(简化)
当然,除此之外,Protocol Buffers 比 JSON 更快。
这种方法也有一些缺点:将我们的内部类型转换为生成的 protobuf 结构体需要更多工作——例如,url::Url
必须先转换为 String——而当我们使用 serde-json 序列化时,任何实现 serde::Serialize
的结构体都只需一行代码就可以跨越 FFI 障碍。它还会在我们的构建过程中增加一个步骤,尽管它很容易集成。
我应该提的一点是:由于我们以一个单元的形式发布了这些二进制流的生产者和消费者,因此我们可以透明地更改我们的数据交换格式,而不会影响我们的 Android 和 iOS 消费者。
展望未来
展望未来,可能存在一个可以用于在 FFI 上交换数据的更高级别系统,也许可以基于 Rust 宏。也有人谈论使用 FlatBuffers 来进一步提高性能。在我们的案例中,Protobufs 在易用性、性能和相对安全性之间取得了平衡。
到目前为止,我们的组件同时出现在 iOS、Firefox 和 Lockbox 上,以及 Lockbox Android 上,并且很快将在我们即将推出的新 Android 浏览器中提供。
Firefox iOS 已经开始 氧化,用我们用 Rust 编写的密码同步引擎替换了他们的密码同步引擎。计划最终在 Firefox 桌面版上也这样做。
如果你有兴趣帮助我们构建 Firefox 同步的未来以及更多功能,或者只是关注我们的进展,请访问 应用程序服务 Github 代码库。
7 条评论