1.2. 与Apache Thrift的应用集成

无论你的应用程序是否使用多平台和语言,它的操作很可能基于网络和时间跨越多个进程。有时,这些进程需要通过磁盘上的文件,内存中的缓冲区,或者网络进行通信。和 IPC (Interprocess communication) 相关的两个核心概念是:

  1. 类型序列化
  2. 服务实现

让我们依次来考虑这两个问题。

1.2.1 类型序列化

序列化是任何跨平台/语言通信的基本功能。例如,想象一下一个音乐行业应用程序,使用 NATS 作为消息系统,在进程之间移动歌曲数据。使用 NATS,该团队可以在他们用 Java 或 Python 编写的远程进程之间快速发送/接收消息。问题是,当歌曲信息在进程之间传递时,程序能读懂另一个进程发送的信息吗?Python 对象和 Java 对象在内存中的表示方式不同。如果 Python 进程发送音乐轨道数据的原始内存信息,就会出现问题。

为了解决这个问题,我们需要在信息传输平台上创建一个数据序列化层。有人可能会问,为什么不用 JSON 的形式来回发送一切呢?使用标准格式(例如 JSON) 可能是解决方案的一部分; 然而,我们仍必须回答这样的问题:

当发送多字段消息时,数据字段如何排序? 当字段缺失时,会发生什么?一个不直接支持某种数据类型的语言在接收该数据类型的时,会做些什么?这些以及其他的问题,JSON, YAML 或 XML 等数据布局规范都无法解决。对于同一个数据集,不同语言经常产生不同但合法的格式。

  • IDL and Types

Apache Thrift提供了一个模块化的序列化框架来解决这些问题。 通过Apache Thrift,开发者在接口定义语言(IDL)中定义抽象数据类型。 然后这个IDL可以被编译成任何支持的语言的源代码。 生成的代码为用户定义的所有类型提供完整的序列化和反序列化逻辑。 Apache Thrift确保任何语言编写的类型都可以被任何其他语言读取。下面的代码显示了一个假设的音乐应用程序的Apache Thrift IDL 类型定义。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
namespace * music

enum PerfRightsOrg {
    ASCAP = 1
    BMI = 2
    SESAC = 3
    Other = 4
}

typedef double Minutes

struct MusicTrack {
    1: string title
    2: string artist
    3: string publisher
    4: string composer
    5: Minutes duration
    6: PerfRightsOrg pro
}

有人抱怨说创建 IDL 是一个额外的步骤,减缓了开发进程。我发现恰恰相反。IDL 强迫你独立地小心考虑你的接口设计,而无需关心嘈杂的实现代码。这可能是你花在系统设计上最终要的时间。IDL 也是轻量级,易于修改和实验,并经常作为业务方面的通信工具而发挥作用。

用户可能会说无模式的系统更加灵活,而 IDL 很坚硬。事实上是,无论你是否记录了你的模式,当你读取和解释数据的时候,你都会需要一个模式。隐含模式会是非常危险的线上错误的来源,而且会给想要操作数据或扩展系统的人造成负担。

如果,除了读取和写入的代码,除此之外你对你读写的数据没有任何定义。当你想扩展系统的时候进展就会很缓慢。整个系统有多少代码依赖于这样的隐含模式?你如何改变这样一个东西?

许多无模式的 NoSQL 系统的流行,为 IDL 创造了另外一个角色。你现在可以在一个地方记录你的类型,并在使用消息系统和 NoSQL 存储系统(Redis, MongoDB 或其他 NoSQL 系统)的服务调用中使用这些类型。

有几个系统反其道而行之,从一个给定的编码方案中生成它们的模式。注释驱动的系统,如Java的JAX-RS,就以这种方式工作。这种方法很容易让实现细节影响接口定义,使可移植性和清晰性受到限制。一般来说,修改实现代码要比修改IDL的工作量大得多。另外,你不能保证另一个供应商的代码生成器会从一个外部模式中创建兼容的代码。当多个供应商参与到一个通信解决方案中时,这就是一个问题。

Apache Thrift通过提供唯一定义 Schema 来源,即 IDL ,避免了许多这些问题。Apache Thrift提供了独立于供应商的支持,在广泛的编程语言中使用单一的IDL,并且Apache Thrift的跨语言测试套装随着框架的发展不断地验证互操作性。

  • Interface evolution (接口演变)

IDL 创建了一个各方都可以依赖的契约,代码生成器可以用来创建工作的序列化操作,确保契约得到遵守。然而,IDL模式不需要很脆(brittle)。Apache Thrift IDL支持一系列的接口演化功能,如果使用得当,允许添加和删除字段,改变类型,等等。

对接口进化的支持大大简化了持续的软件维护和扩展的任务。现代工程意识,如微服务、持续集成(CI)和持续交付(CD)要求系统支持增量改进,而不影响平台的其他部分。那些不提供接口进化形式的工具,在改变时往往会 “破坏世界”。在这样的系统中,改变接口意味着所有使用该接口的客户端和服务器必须重写或重新编译,然后在大爆炸中重新部署。

Apache Thrift的接口进化功能允许多个接口版本在一个操作环境中无缝共存。这使得增量更新成为可能,使CI/CD管道成为可能,并使各个敏捷团队能够以他们自己的节奏交付商业价值。

  • Modular serialization

Apache Thrift提供了可插拔的序列化器,被称为协议,允许你使用几种序列化格式中的任何一种进行数据交换,包括 Binary (速度快),compact(体积小),以及JSON(可读性好)。即使你改变了序列化协议,同样的契约(IDL)也可以保持原样。这种模块化的方法也允许添加自定义的序列化协议。由于Apache Thrift是社区管理和开源的,你可以很容易地改变或增强功能,并在需要时向上游推送(Apache Thrift项目始终欢迎补丁)。