写点什么

Thrift 学习笔记

用户头像
U+2647
关注
发布于: 2021 年 04 月 06 日

1. Thrift 简介

Thrift 是有 Facebook 开源的一套 RPC 框架, 支持多种语言,它是通过自身的 中间语言(IDL),并借助代码生成引擎来生成各种主流语言的代码模板

2. IDL 语言

Thrift 采用 IDL(Interface Definition Language)来定义通用的服务接口,然后通过 Thrift 提供的编译器,可以将服务接口编译成不同语言编写的代码,通过这个方式来实现跨语言的功能。


IDL 中有基本类型、结构体类型、容器类型、枚举类型、异常类型、服务类型等六种类型。熟悉了这些常用类型后基本上可以应付日常开发。

2.1 基本类型:

  • bool: 布尔值

  • byte: 8 位有符号整数

  • i16: 16 位有符号整数

  • i32: 32 位有符号整数

  • i64: 64 位有符号整数

  • double: 64 位浮点数

  • string: UTF-8 编码的字符串

  • binary: 二进制串

2.2 struct(结构体):

类似于 C 语言中的结构体。在 Java 中就是 POJO,struct 类型有以下几个要求:


  1. struct 不能继承,但是可以嵌套,不能嵌套自己。

  2. 其成员都是有明确类型

  3. 成员是被正整数编号过的,其中的编号使不能重复的,这个是为了在传输过程中编码使用。

  4. 成员分割符可以是逗号(,)或是分号(;),而且可以混用

  5. 字段会有 optional 和 required 之分和 protobuf 一样,但是如果不指定则为无类型–可以不填充该值,但是在序列化传输的时候也会序列化进去,optional 是不填充则部序列化,required 是必须填充也必须序列化。

  6. 每个字段可以设置默认值

  7. 同一文件可以定义多个 struct,也可以定义在不同的文件,进行 include 引入。


例如:


struct User{  1: required string name, //改字段必须填写  2: optional i32 age = 0; //默认值  3: bool gender //默认字段类型为optional}
复制代码

2.3 Container (容器)

  • list<t>: 元素类型为 t 的有序表,容许元素重复。对应 Java 的 ArrayList

  • set<t>: 元素类型为 t 的无序表,不容许元素重复。对应 Java 的 HashSet

  • map<t, t>: 键类型为 t,值类型为 t 的 kv 对,键不容许重复。对应 Java 的 HashMap


例如:


struct Test {  1: map<string, User> usermap,  2: set<i32> intset,  3: list<double> doublelist}
复制代码

2.4 enum (枚举):

Thrift 不支持枚举类嵌套,枚举常量必须是 32 位的正整数。


例如:


enum HttpStatus {  OK = 200,  NOTFOUND=404}
复制代码

2.5 Exception (异常):

异常在语法和功能上类似于结构体,不过使用的关键字是 exception。对应 Java 的 Exception


例如:


exception MyException {    1: i32 errorCode,    2: string message}
复制代码

2.6 Service (服务定义类型):

即我们需要提供的服务接口。


例如:


service HelloService {    i32 sayInt(1:i32 param)    string sayString(1:string param)    bool sayBoolean(1:bool param)    void sayVoid()}
复制代码

2.7 常量类型

在 Java 中我们可能还会经常用到常量类型,在 IDL 中 常亮类型可以使用 const 关键字。


例如:


const i32 const_int = 1;
复制代码

2.8 Namespace (名字空间)

IDL 中的命名空间即是 Java 中的包名


例如:


namespace java com.example.test
会被转换成:
package com.example.test
复制代码

2.9 注释

IDL 支持 单行注释和多行注释


/**  * 这里是多行注释 *  */
// 这里是单行注释
复制代码

2.10 Include(导入)

类似于 Java 中的 import 在本文件中引入其他文件中定义的内容。


**注意:**thrift 文件名要用双引号包含,末尾没有逗号或者分号。


例如:


include "test.thrift"   ...struct HelloTest {    1: in32 uid;     ...}
复制代码

3. Java 版的 Hello World

3.1 安装

由于 Thrift 需要使用代码生成引擎来将 Thrift 代码转换成其他语言(Java)的代码,所以需要在本地安装一下 Thrift


注意:本地安装的 Thrift 版本最好和生产环境保持一致。不然很可能会出现一些问题


使用以下命令安装:


brew install thrift
复制代码


本人使用的是 Mac OS ,你和可以去 Thrift 官网,下载对应的安装包进行安装。


最终,如果你在命令行下执行下面的命令,出现 Thrift 版本号,就说明安装成功了。


> thrift -versionThrift version 0.12.0
复制代码


IDEA 默认是不支持 Thrift 所以需要安装 Thrift Support 插件。

3.2 配置

安装好插件,重启后就能在 File -> new -> Project... 菜单下 看到 Thrift 工程了。



创建完成 Thrift 项目后,还需要配置一下 Thrift 代码生成引擎相关的东西。


打开菜单 File - > Project Structure... -> Project Settings -> Facets ,选择 Thrift



然后会让你代码输出的位置。


3.3 创建 Thrift 文件

新建 HelloService.thrift,在菜单中可能找不到创建 thrift 文件的菜单,直接新建 File 然后以 thrift 作为文件后缀就可以。


内容如下:


namespace java com.zdran.test.thriftservice ThriftHelloService{    string sayHello(1:string userName)}
复制代码


在这个接口文件中我们定义了一个 sayHello 的接口,它接收一个 String 类型的参数,并返回一个 String 类型的值。


现在我需要编译该 thrift 文件,然后就会自动生成我们需要的代码。


我们可以直接在控制台执行以下命令。


thrift -gen java HelloService.thrift
复制代码


然后就能在gen-java目录中看到生成后的 java 文件了。


还有一种办法,就是将 thrift 编译器添加到右键菜单里,直接在 IDEA 里编译。


打开 Preferences... 菜单,设置好你的 Thrift 的安装目录,然后勾选上下面的这个选项,就可以在 IDEA 里直接编译 thrift 文件了。



注意:thrift 文件 必须要在 src 目录下,或者手动设置的 source 目录(IDEA 显示蓝色的文件夹)下才会有 Recompile 菜单


PS: 搞了半天,最后可以编译了,结果报错:


Error:Internal error: (java.util.concurrent.ExecutionException) com.intellij.util.xmlb.XmlSerializationException: Cannot deserialize class com.intellij.plugins.thrift.config.ThriftCompilerOptionsjava.util.concurrent.ExecutionException: com.intellij.util.xmlb.XmlSerializationException: Cannot deserialize class com.intellij.plugins.thrift.config.ThriftCompilerOptions    ... ... 
复制代码


也不知道是 IDEA 的问题,还是插件的问题,网上搜了一下啊,貌似没有遇到这个问题的,有时间再研究吧,直接在控制台编译好,然后我们再新建一个 Maven 项目。并添加 thrift 依赖


    <dependencies>        <dependency>            <groupId>org.apache.thrift</groupId>            <artifactId>libthrift</artifactId>            <version>0.12.0</version>        </dependency>    </dependencies>
复制代码


然后把上面生成的 java 文件复制到 对应的包下面。


然后我再创建一个实现类ThriftHelloServiceImpl.java,来实现我们定义好的接口。内容如下:


package com.test.thrift;import org.apache.thrift.TException;
public class ThriftHelloServiceImpl implements ThriftHelloService.Iface { @Override public String sayHello(String userName) throws TException {
return "hello "+ userName; }}
复制代码

3.4 创建服务端和客户端类

我们这里采用 Main 方法的形式来进行客户端与服务端的通信,即启动两个 Main 方法,分别表示服务端与客户端。


首先创建服务端的 Main 方法,代码如下:


public class ServiceMain {    public static void main(String[] args) throws Exception {        ServerSocket serverSocket = new ServerSocket(9090);        TServerSocket serverTransport = new TServerSocket(serverSocket);        ThriftHelloService.Processor processor =                new ThriftHelloService.Processor<ThriftHelloService.Iface>(new ThriftHelloServiceImpl());
TBinaryProtocol.Factory protocolFactory = new TBinaryProtocol.Factory(); TSimpleServer.Args tArgs = new TSimpleServer.Args(serverTransport); tArgs.processor(processor); tArgs.protocolFactory(protocolFactory);
TServer tServer = new TSimpleServer(tArgs); System.out.println("启动 Thrift 服务端"); tServer.serve(); }}
复制代码


然后再创建客户端的 Main 方法,代码如下:


public class ClientMain {    public static void main(String[] args) {        TTransport transport = null;        try {            transport = new TSocket("127.0.0.1", 9090, 6000);            TProtocol protocol = new TBinaryProtocol(transport);            ThriftHelloService.Client client = new ThriftHelloService.Client(protocol);            transport.open();
String result = client.sayHello("thrift-1"); System.out.println(result); } catch (TException e) { e.printStackTrace(); } finally { if (null != transport) { transport.close(); } } }}
复制代码


然后启动服务端的 Main 方法,再启动客户端的 Main 方法,会看到 hello thrift-1,说明 RPC 通信成功了。

4. 关于 Thrift 生成好的 Java 代码

可能你会很奇怪,我们仅仅在 .thrift 文件中定义了一个接口,但是在生成后的 .java 文件中,却生成了大量的代码。


其实我们没必要太过关注生成后的代码,主要关注下面几个东西就可以了。


服务端关注以下两个接口类:


  • ThriftHelloService.Iface


这个是服务端提供同步调用的接口。就是说,你实现的这个接口下面的方法,都会采用同步的方式调用。


  • ThriftHelloService.AsyncIface


同样,如果客户端和服务端需要采用异步的方式通信,服务端就需要实现 AsyncIface 下的接口。


同样,消费端也有两个 Client 一个是同步调用的,一个是异步调用的。


  • ThriftHelloService.Client 执行同步调用。

  • ThriftHelloService.AsyncClient 执行异步调用。

发布于: 2021 年 04 月 06 日阅读数: 93
用户头像

U+2647

关注

evolving code monkey 2018.11.05 加入

https://zdran.com/

评论

发布
暂无评论
Thrift 学习笔记