1. 概述

本文将介绍如何使用 Apache Thrift 框架开发跨平台客户端-服务器应用程序。我们将涵盖以下核心内容:

  • 使用 IDL 定义数据类型和服务接口
  • 安装库并生成多语言源代码
  • 在特定语言中实现定义的接口
  • 实现客户端/服务器软件

如果只想看实战案例,可直接跳转到第 5 节。

2. Apache Thrift 简介

Apache Thrift 最初由 Facebook 开发团队创建,现由 Apache 软件基金会维护。

与专注于跨平台对象序列化/反序列化的 Protocol Buffers 相比,Thrift 的核心优势在于系统组件间的通信层

Thrift 使用特殊的接口描述语言(IDL)定义数据类型和服务接口,这些定义存储在 *.thrift 文件中,作为编译器输入,生成不同编程语言的客户端和服务器源代码。

在项目中使用 Apache Thrift,需添加 Maven 依赖:

<dependency>
    <groupId>org.apache.thrift</groupId>
    <artifactId>libthrift</artifactId>
    <version>0.10.0</version>
</dependency>

最新版本可在 Maven 仓库 查找。

3. 接口描述语言(IDL)

如前所述,IDL 允许以语言无关的方式定义通信接口。以下是当前支持的类型:

3.1 基本类型

  • bool – 布尔值(true/false)
  • byte – 8 位有符号整数
  • i16 – 16 位有符号整数
  • i32 – 32 位有符号整数
  • i64 – 64 位有符号整数
  • double – 64 位浮点数
  • string – UTF-8 编码的文本字符串

3.2 特殊类型

  • binary – 未编码字节序列
  • optional – Java 8 的 Optional 类型

3.3 结构体(Structs)

Thrift 结构体相当于 OOP 语言中的类,但不支持继承。结构体包含一组强类型字段,每个字段有唯一名称标识符。字段可添加注解(如数字字段 ID、可选默认值等)。

3.4 容器

Thrift 容器是强类型集合:

  • list – 有序元素列表
  • set – 无序唯一元素集合
  • map<type1,type2> – 严格唯一键到值的映射

容器元素可以是任何有效 Thrift 类型。

3.5 异常

异常在功能上等同于结构体,但继承自原生异常类。

3.6 服务

服务是使用 Thrift 类型定义的通信接口,由一组命名函数组成,每个函数包含参数列表和返回类型。

4. 源代码生成

4.1 语言支持

当前支持的语言列表很长,包括:

  • C++
  • C#
  • Go
  • Haskell
  • Java
  • JavaScript
  • Node.js
  • Perl
  • PHP
  • Python
  • Ruby

完整列表见官方文档

4.2 使用可执行文件

下载最新版本,按需构建安装,使用以下语法:

cd path/to/thrift
thrift -r --gen [LANGUAGE] [FILENAME]

其中 [LANGUAGE] 是支持的语言,[FILENAME] 是 IDL 定义文件。

注意 -r 标志:当检测到 *.thrift 文件包含其他文件时,会递归生成代码。

4.3 使用 Maven 插件

pom.xml 中添加插件:

<plugin>
   <groupId>org.apache.thrift.tools</groupId>
   <artifactId>maven-thrift-plugin</artifactId>
   <version>0.1.11</version>
   <configuration>
      <thriftExecutable>path/to/thrift</thriftExecutable>
   </configuration>
   <executions>
      <execution>
         <id>thrift-sources</id>
         <phase>generate-sources</phase>
         <goals>
            <goal>compile</goal>
         </goals>
      </execution>
   </executions>
</plugin>

执行命令:

mvn clean install

⚠️ 注意:此插件已停止维护,更多信息见项目主页

5. 客户端-服务器应用实战

5.1 定义 Thrift 文件

创建包含异常和结构的简单服务:

namespace cpp com.baeldung.thrift.impl
namespace java com.baeldung.thrift.impl

exception InvalidOperationException {
    1: i32 code,
    2: string description
}

struct CrossPlatformResource {
    1: i32 id,
    2: string name,
    3: optional string salutation
}

service CrossPlatformService {

    CrossPlatformResource get(1:i32 id) throws (1:InvalidOperationException e),

    void save(1:CrossPlatformResource resource) throws (1:InvalidOperationException e),

    list <CrossPlatformResource> getList() throws (1:InvalidOperationException e),

    bool ping() throws (1:InvalidOperationException e)
}

语法简洁直观,我们定义了命名空间(按语言划分)、异常类型、结构体和服务接口。保存为 service.thrift 文件。

5.2 编译生成代码

运行编译器生成代码:

thrift -r -out generated --gen java /path/to/service.thrift

-out 标志指定输出目录。成功后 generated 目录包含三个文件:

  • CrossPlatformResource.java
  • CrossPlatformService.java
  • InvalidOperationException.java

再生成 C++ 版本:

thrift -r -out generated --gen cpp /path/to/service.thrift

现在我们获得了同一服务接口的两种语言实现(Java 和 C++)。

5.3 实现服务接口

虽然 Thrift 已完成大部分工作,仍需实现 CrossPlatformService。只需实现 CrossPlatformService.Iface 接口:

public class CrossPlatformServiceImpl implements CrossPlatformService.Iface {

    @Override
    public CrossPlatformResource get(int id) 
      throws InvalidOperationException, TException {
        return new CrossPlatformResource();
    }

    @Override
    public void save(CrossPlatformResource resource) 
      throws InvalidOperationException, TException {
        saveResource();
    }

    @Override
    public List<CrossPlatformResource> getList() 
      throws InvalidOperationException, TException {
        return Collections.emptyList();
    }

    @Override
    public boolean ping() throws InvalidOperationException, TException {
        return true;
    }
}

5.4 编写服务器

构建跨平台应用需要服务器。Thrift 自带的通信框架让实现变得简单:

public class CrossPlatformServiceServer {
    public void start() throws TTransportException {
        TServerTransport serverTransport = new TServerSocket(9090);
        server = new TSimpleServer(new TServer.Args(serverTransport)
          .processor(new CrossPlatformService.Processor<>(new CrossPlatformServiceImpl())));

        System.out.print("Starting the server... ");

        server.serve();

        System.out.println("done.");
    }

    public void stop() {
        if (server != null && server.isServing()) {
            System.print("Stopping the server... ");

            server.stop();

            System.out.println("done.");
        }
    }
}

关键步骤:

  1. 定义传输层(TServerTransport 实现),指定监听端口
  2. 选择服务器实现:
    • TSimpleServer – 简单服务器
    • TThreadPoolServer – 多线程服务器
    • TNonblockingServer – 非阻塞多线程服务器
  3. 提供处理器(由 Thrift 生成的 CrossPlatformService.Processor

5.5 编写客户端

客户端实现同样简单:

TTransport transport = new TSocket("localhost", 9090);
transport.open();

TProtocol protocol = new TBinaryProtocol(transport);
CrossPlatformService.Client client = new CrossPlatformService.Client(protocol);

boolean result = client.ping();

transport.close();

客户端步骤:

  1. 定义传输层并指向服务器
  2. 选择协议(如 TBinaryProtocol
  3. 初始化 Thrift 生成的客户端实例(CrossPlatformService.Client
  4. 直接调用 *.thrift 中定义的方法(如 client.ping()

6. 总结

本文介绍了 Apache Thrift 的核心概念和使用步骤,并通过实战案例展示了如何构建跨平台服务。所有示例代码可在 GitHub 仓库 获取。


原始标题:Working with Apache Thrift