Contents

Apache Thrift 简介

1.概述

在本文中,我们将了解如何借助名为Apache Thrift 的 RPC 框架开发跨平台的客户端-服务器应用程序。

我们将涵盖:

  • 使用 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 允许以中性语言定义通信接口。您将在下面找到当前支持的类型。

3.1.基本类型

  • bool – 一个布尔值(真或假)
  • byte – 一个 8 位有符号整数
  • i16 – 16 位有符号整数
  • i32 – 32 位有符号整数
  • i64 – 64 位有符号整数
  • double – 64 位浮点数
  • string – 使用 UTF-8 编码编码的文本字符串

3.2. 特殊类型

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

3.3. 结构

Thrift*structs *相当于 OOP 语言中的类,但没有继承。*structs *有一组强类型字段,每个字段都有一个唯一的名称作为标识符。字段可能有各种注释(数字字段 ID、可选的默认值等)。

3.4. 容器

Thrift 容器是强类型容器:

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

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

3.5. 例外

异常在功能上等同于structs,只是它们继承自本机异常。

3.6. 服务

服务实际上是使用 Thrift 类型定义的通信接口。它们由一组命名函数组成,每个函数都有一个参数列表和一个返回类型。

4. 源代码生成

4.1. 语言支持

当前支持的语言有一长串:

  • C++
  • C#
  • 哈斯克尔
  • 爪哇
  • Javascript
  • 节点.js
  • Perl
  • PHP
  • Python
  • Ruby

您可以在此处 查看完整列表。

4.2. 使用库的可执行文件

只需下载最新版本 ,如有必要,构建并安装它,并使用以下语法:

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

在上面设置的命令中,[LANGUAGE]是支持的语言之一,[FILENAME ] 是具有 IDL 定义的文件。

注意*-r标志。它告诉 Thrift 一旦注意到包含在给定的.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.blogdemo.thrift.impl
namespace java com.blogdemo.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*来指定生成文件的输出目录。如果您没有收到任何错误,生成的目录将包含 3 个文件:

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

让我们通过运行以下命令生成服务的 C++ 版本:

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

现在我们得到相同服务接口的 2 个不同的有效实现(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. 编写服务器

正如我们所说,我们想要构建一个跨平台的客户端-服务器应用程序,因此我们需要一个服务器。Apache 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.out.print("Stopping the server... ");
            server.stop();
            System.out.println("done.");
        }
    }
}

首先是定义一个传输层,实现TServerTransport接口(或者更准确地说是抽象类)。既然我们在谈论服务器,我们需要提供一个端口来监听。然后我们需要定义一个TServer实例并选择一个可用的实现:

  • TSimpleServer – 用于简单服务器
  • TThreadPoolServer – 用于多线程服务器
  • TNonblockingServer – 用于非阻塞多线程服务器

最后,为选择的服务器提供一个处理器实现,它已经由 Thrift 为我们生成,即CrossPlatofformService.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();

从客户的角度来看,这些操作非常相似。

首先,定义传输并将其指向我们的服务器实例,然后选择合适的协议。唯一的区别是我们在这里初始化了客户端实例,它再次由 Thrift 生成,即CrossPlatformService.Client类。

由于它基于*.thrift*文件定义,我们可以直接调用那里描述的方法。在这个特定的示例中,client.ping()将对服务器进行远程调用,该服务器将以true响应。