Thrift学习笔记 | 青训营

266 阅读9分钟

1. IDL

接口描述语言(Interface description language,缩写IDL),是用来描述软件组件介面的一种计算机语言。IDL通过一种独立于编程语言的方式来描述接口,使得在不同平台上运行的对象和用不同语言编写的程序可以相互通信交流;比如,一个组件用C++写成,另一个组件用Java写成。

IDL通常用于远程调用软件。在这种情况下,一般是由远程客户终端调用不同操作系统上的对象组件,并且这些对象组件可能是由不同计算机语言编写的。IDL建立起了两个不同操作系统间通信的桥梁。

在IDL基础上开发出来的软件系统有SunONC RPCThe Open Group分散式运算环境IBM系统物件模型Object Management GroupCORBA,和SOAP(用于Web service)。

2. Thrift

2.1 介绍

Apache Thrift(也称为Apache Thrift Framework或Thrift)是一个跨语言的服务端和客户端开发框架,用于构建高效、可扩展和跨平台的RPC(远程过程调用)服务。它最初由Facebook开发,后来捐赠给Apache基金会,并成为了Apache顶级项目之一。

2.2 基础

Thrift的主要目标是解决不同语言之间的通信问题,使不同平台上的应用能够轻松地进行跨语言的远程通信。它的工作原理基于以下关键特点:

  1. IDL(接口定义语言) :Thrift使用自定义的IDL语言来定义数据结构和服务接口。IDL描述了数据类型、函数和接口的定义,这些定义用于生成各种目标语言的代码。
  2. 代码生成:基于IDL文件,Thrift提供代码生成工具,能够生成不同编程语言的客户端和服务端代码。这些生成的代码负责处理数据的序列化、网络通信和反序列化等细节。
  3. 多语言支持:Thrift支持多种编程语言,包括但不限于Java、C++、Python、Go、Ruby等。这使得不同语言的应用可以轻松地进行跨语言通信。
  4. 多协议支持:Thrift支持多种传输协议和数据序列化格式,如二进制协议、JSON、XML等。这允许开发人员根据需求选择合适的协议和格式。
  5. 异步通信:Thrift支持异步通信模式,允许客户端发起非阻塞式的远程调用,从而在高并发情况下提高性能。
  6. 灵活的传输层:Thrift提供多种传输层选项,包括Socket、HTTP、Framed、Memory Buffer等,使得可以根据应用的要求选择适当的传输方式。
  7. 拓展性:通过定义IDL文件,可以轻松地添加新的数据类型和服务接口,从而实现系统的拓展和演化。

2.3 基础语法

Thrift的基础语法是通过IDL(接口定义语言)来定义数据类型和服务接口的。以下是一些常见的Thrift基础语法元素的说明:

  1. 命名空间(Namespace) :用于定义命名空间,防止不同模块之间的命名冲突。例如:

    namespace java com.example
    
  2. 数据类型:Thrift支持多种数据类型,包括基本数据类型(如整数、浮点数)、容器类型(如列表、集合、映射)和自定义结构体。例如:

    struct Person {
        1: required string name,
        2: optional i32 age,
    }
    
  3. 枚举类型:定义一个枚举类型,表示一组相关的常量值。例如:

    enum Gender {
        MALE,
        FEMALE,
    }
    
  4. 异常类型:定义自定义异常,用于在服务调用中抛出错误。例如:

    exception NotFoundException {
        1: required string message,
    }
    
  5. 服务接口:定义一个服务接口,包含一组需要远程调用的方法。每个方法包括名称、参数和返回值。例如:

    service MyService {
        i32 add(1: i32 num1, 2: i32 num2),
        string sayHello(1: string name),
    }
    
  6. 注释:支持单行和多行注释。例如:

    // 这是单行注释
    /*
       这是
       多行
       注释
    */
    

Thrift的IDL文件描述了数据结构和服务接口的定义。基于这个IDL文件,Thrift提供了代码生成工具,可以生成不同编程语言的客户端和服务端代码。生成的代码包括数据结构的序列化和反序列化、网络通信以及服务接口的具体实现等。开发人员可以通过生成的代码在不同语言中实现远程过程调用。

2.4 thrift的使用实例

当使用Thrift生成Go代码时,步骤如下:

  1. 安装Thrift:从Thrift的官方网站(thrift.apache.org/download)下载适合你操作系统的Thrift编译器,并按照官方文档的说明进行安装。

  2. 创建IDL文件:在你的工作目录中创建一个Thrift的IDL文件,例如example.thrift

  3. 定义服务接口:在IDL文件中定义你的服务接口。以下是一个简单的示例:

    namespace go tutorial
    ​
    service Calculator {
        i32 add(1: i32 num1, 2: i32 num2),
    }
    
  4. 生成Go代码:在终端或命令行中,进入包含IDL文件的目录,然后运行以下命令以生成Go代码:

    thrift --gen go example.thrift
    

    这将在当前目录下生成一个名为gen-go的子目录,其中包含生成的Go代码。

    生成的gen-go目录包含了根据Thrift定义的IDL文件生成的Go代码。下面是一个可能的gen-go目录结构示例:

    gen-go/
    └── tutorial
        ├── Calculator-consts.go
        ├── Calculator.go
        ├── Calculator-remote
        │   ├── Calculator.go
        │   └── Shared.go
        ├── Calculator_types.go
        ├── Calculator-Types.go
        └── tutorial-consts.go
    

    在这个示例中,gen-go目录下的tutorial子目录对应于Thrift文件中定义的namespace go tutorial。在tutorial目录中,你会看到多个与服务接口和数据类型相关的Go代码文件。

    • Calculator-consts.go:包含常量定义,例如枚举类型中的常量。

    • Calculator.go:包含服务接口定义,以及服务处理器和工厂的实现。

    • Calculator-remote目录:包含远程调用相关的代码,用于客户端调用服务端方法。

      • Calculator.go:实现了客户端调用远程方法的代码。
      • Shared.go:包含共享的远程调用函数和结构体,用于客户端和服务端之间的通信。
    • Calculator_types.go:包含数据类型的定义,以及与数据类型相关的函数和方法。

    • Calculator-Types.go:类似于Calculator_types.go,其中可能包含了一些其他的自动生成的代码。

    • tutorial-consts.go:包含与IDL文件中定义的常量相关的Go代码。

    每个文件的内容可能会因IDL文件中的定义而有所不同。在实际项目中,你可以根据需要使用这些生成的代码来实现你的服务端和客户端逻辑。记得在你的Go代码中导入相应的包(import tutorial)以便使用这些生成的类型和方法。

  5. 整合Go代码:将生成的Go代码整合到你的Go项目中。可以将gen-go目录复制到你的项目目录下,或者将其中的文件复制到你的项目的合适位置。

    在将Thrift生成的Go代码整合到项目中后,整个项目的文件结构可能会如下所示:

    your_project/
    ├── gen-go/
    │   └── tutorial/
    │       ├── Calculator-consts.go
    │       ├── Calculator.go
    │       ├── Calculator-remote/
    │       │   ├── Calculator.go
    │       │   └── Shared.go
    │       ├── Calculator_types.go
    │       ├── Calculator-Types.go
    │       └── tutorial-consts.go
    ├── main.go
    ├── README.md
    └── go.mod
    

    解释说明:

    • your_project/:项目的根目录,你的项目文件将放置在这里。
    • gen-go/:这是Thrift生成的Go代码的目录。根据你的IDL文件中的命名空间,可能会有其他的子目录,如tutorial
    • main.go:这是你的Go项目的主要入口文件,你可以在其中编写你的服务端逻辑。
    • README.md:项目的说明文档,可以包含项目的简要描述和使用方法。
    • go.mod:这是Go模块文件,记录了你的项目的依赖关系。

    在项目中,你可以在main.go文件中实现服务端逻辑,使用Thrift生成的代码来定义和实现服务接口。同时,你也可以在其他Go文件中编写客户端逻辑,以便与服务端进行远程过程调用。

    你可以根据项目的需要,将其他文件和目录添加到项目中,例如配置文件、测试文件等。请根据实际情况进行适当的组织和管理,以便保持代码的可读性和可维护性。

  6. 创建服务端代码:在你的Go项目中,可以使用生成的Go代码来实现Thrift定义的服务端。以下是一个示例:

    package main
    ​
    import (
        "fmt"
        "git.apache.org/thrift.git/lib/go/thrift"
        "tutorial" // 这是你生成的Go代码包的路径
    )
    ​
    type CalculatorHandler struct{}
    ​
    func (c *CalculatorHandler) Add(num1, num2 int32) (int32, error) {
        return num1 + num2, nil
    }
    ​
    func main() {
        handler := &CalculatorHandler{}
        processor := tutorial.NewCalculatorProcessor(handler)
    ​
        transportFactory := thrift.NewTTransportFactory()
        protocolFactory := thrift.NewTBinaryProtocolFactoryDefault()
    ​
        serverTransport, err := thrift.NewTServerSocket(":9090")
        if err != nil {
            fmt.Println("Error:", err)
            return
        }
    ​
        server := thrift.NewTSimpleServer4(processor, serverTransport, transportFactory, protocolFactory)
        fmt.Println("Starting the server...")
        server.Serve()
    }
    

    在这个示例中,我们实现了一个简单的服务端,处理Calculator服务接口的Add方法。

  1. 创建客户端代码:当客户端调用Thrift定义的函数时,需要使用生成的客户端代码来构建客户端应用。以下是一个简单的示例,演示了如何使用生成的Go客户端代码来调用示例中的Calculator服务的Add方法:

    package main
    ​
    import (
        "fmt"
        "git.apache.org/thrift.git/lib/go/thrift"
        "tutorial" // 这是你生成的Go代码包的路径
    )
    ​
    func main() {
        transport, err := thrift.NewTSocket("localhost:9090")
        if err != nil {
            fmt.Println("Error opening socket:", err)
            return
        }
    ​
        protocolFactory := thrift.NewTBinaryProtocolFactoryDefault()
        client := tutorial.NewCalculatorClientFactory(transport, protocolFactory)
    ​
        if err := transport.Open(); err != nil {
            fmt.Println("Error opening transport:", err)
            return
        }
        defer transport.Close()
    ​
        num1 := int32(10)
        num2 := int32(20)
        result, err := client.Add(num1, num2)
        if err != nil {
            fmt.Println("Error calling Add:", err)
            return
        }
    ​
        fmt.Printf("Result of %d + %d = %d\n", num1, num2, result)
    }
    

    在这个示例中,我们使用生成的tutorial包中的客户端代码来构建一个简单的客户端应用。客户端与服务端通信的步骤如下:

    1. 创建一个 TSocket 对象,用于与服务端建立连接。
    2. 创建一个 TBinaryProtocolFactory 对象,用于序列化和反序列化数据。
    3. 创建一个 CalculatorClient 对象,这是通过生成的代码创建的,它封装了远程调用的功能。
    4. 打开与服务端的连接。
    5. 调用 Add 方法并传递参数,获取服务端的响应。
    6. 关闭与服务端的连接。

    通过使用生成的客户端代码,你可以轻松地在客户端应用中实现对服务端的远程调用,以实现分布式系统中的功能协作。