Weiguo's Station

  • 博客首页

  • 文章归档

  • 分类专栏

  • 各种标签

  • 站点搜索

Thrift基础

发表于 2020-03-13 更新于 2021-03-22 分类于 语言框架

Thrift最初由Facebook研发,主要用于各个服务之间的RPC通信,支持跨语言, 常用的语言比如C++, Java, Python, PHP, Ruby等语言都支持。 Thrift是一个典型的CS(客户端/服务端)结构,客户端和服务端可以使用不同的语言开发。 在推荐系统进行Serving的时候,用到这个进行快速的交互。

本文是从Thrift Tutorial及网上的一些资料整理学习得到,版权归原作者。

1. Thrift中的类型

1.1 基本类型

thrift不支持无符号类型,因为很多编程语言不存在无符号类型。

  • byte: 有符号字节
  • i16: 16位有符号整数
  • i32: 32位有符号整数
  • i64: 64位有符号整数
  • double: 64位浮点数
  • string: 字符串

1.2 特殊类型

binary: 无编码字节序列

1.3 结构体 Structs

就像C语言一样,thrift也支持struct类型,目的就是将一些数据聚合在一起,方便传输管理。struct的定义形式如下:

1
2
3
4
5
6
struct Example {
1:i32 number=10,
2:i64 bigNumber,
3:double decimals,
4:string name="thrifty"
}

1.4 容器类型 Containers

集合中的元素可以是除了service之外的任何类型,包括exception。

  • list (类似c++ STL vector、Java ArrayList)
  • set (类似STL set、Java HashSet),PHP不支持set,所以PHP对待set就像是map
  • map (类似STL map、Java HashMap)

1.5 异常 Exceptions

thrift支持自定义exception,规则和struct一样,如下:

1
2
3
4
exception InvalidOperation {
1: i32 what,
2: string why
}

1.6 枚举 Enum

枚举的定义形式和Java的Enum定义差不多,例如:

1
2
3
4
enum Sex {
MALE,
FEMALE
}

1.7 服务 Service

thrift定义服务相当于Java中创建Interface一样,创建的service经过代码生成命令之后就会生成客户端和服务端的框架代码。定义形式如下:

1
2
3
4
5
service <name> {
<returntype> <name>(<arguments>)
[throws (<exceptions>)]
...
}

具体例子如下:

1
2
3
4
5
service StringCache {
void set(1:i32 key, 2:string value),
string get(1:i32 key) throws (1:KeyNotFound knf),
void delete(1:i32 key)
}

2. Thrift中其他操作

2.1 类型重定义 typedef

thrift支持类似C++一样的typedef定义,比如:

1
2
typedef i32 int
typedef i64 long

NOTE: 末尾没有逗号或者分号

2.2 常量 const

thrift也支持常量定义,使用const关键字,例如:

1
2
const i32 MAX_RETRIES_TIME = 10
const string NAME = "ME";

NOTE: 末尾的分号是可选的,可有可无,并且支持16进制赋值

2.3 命名空间

thrift的命名空间相当于Java中的package的意思,主要目的是组织代码。thrift使用关键字namespace定义命名空间,例如:

1
2
3
namespace java tutorial
namespace py tutorial
namespace cpp tutorial

NOTE: 格式是:namespace 语言名 路径,注意末尾不能有分号,不同的语言可以设置不同的namespace

2.4 文件包含

thrift也支持文件包含,相当于C/C++中的include,Java中的import。使用关键字include定义,例如:

1
include "shared.thrift"

2.5 注释

thrift注释方式支持shell风格的注释,支持C/C++风格的注释,即#和//开头的语句都单当做注释,/**/包裹的语句也是注释。

2.6 可选与必选

thrift提供两个关键字required,optional,分别用于表示对应的字段时必填的还是可选的。例如:

1
2
3
4
struct People {
1: required string name;
2: optional i32 age;
}

表示name是必填的,age是可选的。

3. 实例

3.1 生成代码

知道了怎么定义thrift文件之后,我们需要用定义好的thrift文件生成我们需要的目标语言的源码,本文以生成java源码为例。 假设现在定义了如下一个thrift文件,命名为Test.thrift:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
namespace java com.winwill.thrift

enum RequestType {
SAY_HELLO, //问好
QUERY_TIME, //询问时间
}

struct Request {
1: required RequestType type; // 请求的类型,必选
2: required string name; // 发起请求的人的名字,必选
3: optional i32 age; // 发起请求的人的年龄,可选
}

exception RequestException {
1: required i32 code;
2: optional string reason;
}

// 服务名
service HelloWordService {
string doAction(1: Request request) throws (1:RequestException qe); // 可能抛出异常。
}

在终端运行如下命令(前提是已经安装thrift):

1
2
3
thrift --gen java Test.thrift   # 生成jave的代码
thrift --gen py Test.thrift # 生成python的代码
thrift --gen cpp Test.thrift # 生成cpp的代码

则在当前目录会生成一个gen-java/gen-py/gen-cpp目录,该目录下会按照namespace定义的路径名一次一层层生成文件夹,到gen-java/com/winwill/thrift/目录下可以看到生成的4个Java类。

可以看到,thrift文件中定义的enum,struct,exception,service都相应地生成了一个Java类,这就是能支持Java语言的基本的框架代码。

3.2 服务端实现-对thrift中定义的service的接口实现

上面代码生成这一步已经将接口代码生成了,现在需要做的是实现HelloWordService的具体逻辑,实现的方式就是创建一个Java类, implements com.winwill.thrift.HelloWordService,例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package com.winwill.thrift;

import org.apache.commons.lang3.StringUtils;
import org.apache.thrift.TException;
import java.util.Date;

// 实现接口
public class HelloWordServiceImpl implements com.winwill.thrift.HelloWordService.Iface {
// 实现这个方法完成具体的逻辑。
public String doAction(com.winwill.thrift.Request request) throws com.winwill.thrift.RequestException, TException {
// 打印
System.out.println("Get request: " + request);
if (StringUtils.isBlank(request.getName()) || request.getType() == null) {
throw new com.winwill.thrift.RequestException();
}
String result = "Hello, " + request.getName();
if (request.getType() == com.winwill.thrift.RequestType.SAY_HELLO) {
result += ", Welcome!";
} else {
result += ", Now is " + new Date().toLocaleString();
}
return result;
}
}

3.3 启动服务-Server端

上面这个就是服务端的具体实现类,现在需要启动这个服务,所以需要一个启动类,启动类的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package com.winwill.thrift;

import org.apache.thrift.server.TServer;
import org.apache.thrift.server.TSimpleServer;
import org.apache.thrift.transport.TServerSocket;
import java.net.ServerSocket;

public class HelloWordServer {
public static void main(String[] args) throws Exception {
// 创建socket
ServerSocket socket = new ServerSocket(7912);
TServerSocket serverTransport = new TServerSocket(socket);
com.winwill.thrift.HelloWordService.Processor processor = new com.winwill.thrift.HelloWordService.Processor(new HelloWordServiceImpl());
TServer server = new TSimpleServer(processor, serverTransport);
System.out.println("Running server...");
server.serve();
}
}

运行之后看到控制台的输出为:

1
Running server...

3.4 客户端请求-Client端

现在服务已经启动,可以通过客户端向服务端发送请求了,客户端的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package com.winwill.thrift;

import org.apache.thrift.protocol.TBinaryProtocol;
import org.apache.thrift.protocol.TProtocol;
import org.apache.thrift.transport.TSocket;
import org.apache.thrift.transport.TTransport;

public class HelloWordClient {
public static void main(String[] args) throws Exception {
// 创建socket
TTransport transport = new TSocket("localhost", 8888);
// 包装协议
TProtocol protocol = new TBinaryProtocol(transport);
// 创建client
com.winwill.thrift.HelloWordService.Client client = new com.winwill.thrift.HelloWordService.Client(protocol);
// 建立连接
transport.open();

// 第一种请求类型
com.winwill.thrift.Request request = new com.winwill.thrift.Request()
.setType(com.winwill.thrift.RequestType.SAY_HELLO).setName("winwill2012").setAge(24);
System.out.println(client.doAction(request));

// 第二种请求类型
request.setType(com.winwill.thrift.RequestType.QUERY_TIME).setName("winwill2012");
System.out.println(client.doAction(request));

transport.close(); // 请求结束,断开连接
}
}

如果是Python代码的话,原生的socket太慢了,可以再包装一层buffer

1
2
3
4
5
6
7
8
9
10
11
# Make socket
transport = TSocket.TSocket('localhost', 9090)

# Buffering is critical. Raw sockets are very slow
transport = TTransport.TBufferedTransport(transport)

# Wrap in a protocol
protocol = TBinaryProtocol.TBinaryProtocol(transport)

# Create a client to use the protocol encoder
client = com.winwill.thrift.HelloWordService.Client(protocol)

运行客户端代码,得到结果:

1
2
Hello, winwill2012, Welcome!
Hello, winwill2012, Now is 2020-3-13 16:21:45

并且此时,服务端会有请求日志:

1
2
3
Running server...
Get request: Request(type:SAY_HELLO, name:winwill2012, age:24)
Get request: Request(type:QUERY_TIME, name:winwill2012, age:24)

可以看到,客户端成功将请求发到了服务端,服务端成功地将请求结果返回给客户端,整个通信过程完成。

4. 延伸阅读一下gen-java中生成的代码


  1. Thrift Tutorial
  2. thrift入门教程
# Serving
深入理解FTRL-Proximal
保序回归-IsotonicRegression
  • 文章目录
  • 站点概览
WeiguoZHAO

WeiguoZHAO

Welcome to my blog~
87 日志
13 分类
49 标签
GitHub E-Mail
大牛们
  • colah's blog
  • 王喆的Github
  • 刘建平的Github
  • 美团技术团队
  1. 1. Thrift中的类型
    1. 1.1 基本类型
    2. 1.2 特殊类型
    3. 1.3 结构体 Structs
    4. 1.4 容器类型 Containers
    5. 1.5 异常 Exceptions
    6. 1.6 枚举 Enum
    7. 1.7 服务 Service
  2. 2. Thrift中其他操作
    1. 2.1 类型重定义 typedef
    2. 2.2 常量 const
    3. 2.3 命名空间
    4. 2.4 文件包含
    5. 2.5 注释
    6. 2.6 可选与必选
  3. 3. 实例
    1. 3.1 生成代码
    2. 3.2 服务端实现-对thrift中定义的service的接口实现
    3. 3.3 启动服务-Server端
    4. 3.4 客户端请求-Client端
  4. 4. 延伸阅读一下gen-java中生成的代码
© 2021 WeiguoZHAO
0%