Thrift是Facebook开源提供的一个高性能,轻量级RPC服务框架,其产生正是为了满足当前大数据量、分布式、跨语言、跨平台数据通讯的需求。 但是,Thrift并不仅仅是序列化协议,而是一个RPC框架。相对于JSON和XML而言,Thrift在空间开销和解析性能上有了比较大的提升,对于对性能要求比较高的分布式系统,它是一个优秀的RPC解决方案;但是由于Thrift的序列化被嵌入到Thrift框架里面,Thrift框架本身并没有透出序列化和反序列化接口,这导致其很难和其他传输层协议共同使用(例如HTTP)。
典型应用场景和非应用场景
对于需求为高性能,分布式的RPC服务,Thrift是一个优秀的解决方案。它支持众多语言和丰富的数据类型,并对于数据字段的增删具有较强的兼容性。所以非常适用于作为公司内部的面向服务构建(SOA)的标准RPC框架。
不过Thrift的文档相对比较缺乏,目前使用的群众基础相对较少。另外由于其Server是基于自身的Socket服务,所以在跨防火墙访问时,安全是一个顾虑,所以在公司间进行通讯时需要谨慎。 另外Thrift序列化之后的数据是Binary数组,不具有可读性,调试代码时相对困难。最后,由于Thrift的序列化和框架紧耦合,无法支持向持久层直接读写数据,所以不适合做数据持久化序列化协议。
IDL文件举例
struct Address
{
1: required string city;
2: optional string postcode;
3: optional string street;
}
struct UserInfo
{
1: required string userid;
2: required i32 name;
3: optional list<Address> address;
}
IDL语法学习:https://my.oschina.net/helight/blog/195015
安装
先要下载安装相应的IDL文件编译工具。
官方网站:https://thrift.apache.org/download
吐槽:都多少年的东西了,版本号居然是0.13.0
? Are you kidding me?
看看Chrome的版本号,你们真是不会卖!
Windows
比如:http://www.apache.org/dyn/closer.cgi?path=/thrift/0.13.0/thrift-0.13.0.exe
安装 Thrift 的 IDL 编译工具。下载完成后改名为:thrift.exe 并将其放入到系统环境变量下即可使用。
Linux
# 从 github 上下载 thrift 0.13.0 的源码,解压后进入:thrift-0.13.0/compiler/cpp
# 目录执行如下命令完成编译后,将其放入到系统环境变量下即可使用:
$ mkdir cmake-build
$ cd cmake-build
$ cmake ..
$ make
Linux安装过程参考:https://www.cnblogs.com/apocelipes/p/9420825.html
验证是否安装成功:
thrift -version # 看是否看到:Thrift version 0.13.0
Go平台开发包
go get git.apache.org/thrift.git/lib/go/thrift
下面github上面的包最好不要安装,会有很多错误:
// 慎用这个
// go get github.com/apache/thrift/lib/go/thrift/
// 如果不小心装了就到 GOPATH\bin\pkg\mod\github.com 把他们删了。
Go Thrift 示例
本示例的目录结果如下
├─client
│ client.go
├─example
| ......
├─server
│ server.go
└─thrift_file
example.thrift
Thrift接口描述文件,我们使用 Thrift 定义一个接口,该接口实现对传入的数据进行大写的格式化处理。
// example.thrift
namespace py example
struct Data {
1: string text
}
service format_data {
Data do_format(1:Data data),
}
命令行控制台定位到thrift_file
目录下,执行:
thrift -out .. --gen go example.thrift
在 thrift_file
的同级目录下会生成包example
,其中 format_data-remote
是生成的测试代码可以不用特别关注。
服务器端代码:
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
31
32
33
34
35
36
37
38
39
40
41
42
|
// server.go
package server
import (
"context"
"fmt"
"git.apache.org/thrift.git/lib/go/thrift"
"log"
"strings"
"yufa/demo/thrift_demo/example"
)
type FormatDataImpl struct{}
// DoFormat(ctx context.Context, data *Data) (r *Data, err error)
func (fdi *FormatDataImpl) DoFormat(ctx context.Context, data *example.Data)
(r *example.Data, err error) {
var rData example.Data
rData.Text = strings.ToUpper(data.Text)
return &rData, nil
}
const (
HOST = "localhost"
PORT = "8080"
)
func ThriftDemoServer() {
handler := &FormatDataImpl{}
processor := example.NewFormatDataProcessor(handler)
serverTransport, err := thrift.NewTServerSocket(HOST + ":" + PORT)
if err != nil {
log.Fatalln("Error: ", err)
}
transportFactory := thrift.NewTFramedTransportFactory(thrift.NewTTransportFactory())
protocolFactory := thrift.NewTBinaryProtocolFactoryDefault()
server := thrift.NewTSimpleServer4(processor, serverTransport, transportFactory, protocolFactory)
fmt.Println("Running at: ", HOST+":"+PORT)
_ = server.Serve()
}
|
客户端代码:
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
31
32
33
34
35
36
37
38
|
// client.go
package client
import (
"fmt"
"git.apache.org/thrift.git/lib/go/thrift"
"log"
"net"
"yufa/demo/thrift_demo/example"
)
const (
HOST = "localhost"
PORT = "8080"
)
func ThriftDemoClient() {
tSocket, err := thrift.NewTSocket(net.JoinHostPort(HOST, PORT))
if err != nil {
log.Fatalln("tSocket error: ", err)
}
transportFactory := thrift.NewTFramedTransportFactory(thrift.NewTTransportFactory())
transport, _ := transportFactory.GetTransport(tSocket)
protocolFactory := thrift.NewTBinaryProtocolFactoryDefault()
client := example.NewFormatDataClientFactory(transport, protocolFactory)
if err := transport.Open(); err != nil {
log.Fatalln("Error opening: ", HOST+":"+PORT)
}
defer transport.Close()
data := example.Data{Text: "hello world, chende!"}
d, err := client.DoFormat(nil, &data)
if d != nil {
fmt.Println(d.Text)
}
}
|
先启动服务器端,再启动客户端,在客户端控制台会看到:HELLO, WORLD, CHEN DE!
全部转换成了大写。
Go Thrift示例2
接口和数据定义
// demo.thrift
namespace go compute
struct Result {
1: i64 div;
2: i64 mod;
}
service DivMod {
Result DoDivMod(1:i64 arg1, 2:i64 arg2);
}
service MulRange {
string BigRange(1:i64 max)
}
// 编译
thrift -out .. --gen go demo.thrift
实现服务端
步骤如下:
- 实现服务处理接口,在最新版本中,接口第一个参数需要是context.Context,用以支持取消或对RPC调用设置超时。
- 创建 Processor,也就是把实现的接口注册进RPC服务创建 Transport,Transport用于管理网络IO。它是一个interface,可以是TSocket,TServerSocket以及NewTHttpClient*或TTransportFactory.GetTransport返回的对象。
- 创建 Protocol,也就是传输协议,一般使用二进制协议来传输数据,当然也可以选择json或其他格式。
- 创建server,使用前面的Processor,Transport,Protocol以及ip地址来创建服务端。
- 运行server。
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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
|
import (
"context"
"fmt"
"git.apache.org/thrift.git/lib/go/thrift"
"math/big"
"net"
"os"
"yufa/demo/thrift_demo/compute"
)
// computeThrift 实现service中定义的方法
type divmodThrift struct {}
// 每个方法除了定义的返回值之外还要返回一个error,包括定义成void的方法。自定义
// 类型会在名字之后加一条下划线;暂时用不到context,所以忽略
func (d *divmodThrift) DoDivMod(_ context.Context, arg1, arg2 int64)
(*compute.Result_, error) {
divRes := int64(arg1 / arg2)
modRes := int64(arg1 % arg2)
// 生成的用于生成自定义数据对象的函数
res := compute.NewResult_()
res.Div = divRes
res.Mod = modRes
return res, nil
}
// 尽量一个struct对应一个service
type mulrangeThrift struct {}
func (m *mulrangeThrift) BigRange(_ context.Context, max int64) (string, error) {
result := new(big.Int)
result.SetString("1", 10)
result = result.MulRange(2, max)
return result.String(), nil
}
// 启动服务器
func ThriftDemoRPCServer() {
// 创建服务器
serverTransport, err := thrift.NewTServerSocket(net.JoinHostPort("127.0.0.1", "8081"))
if err != nil {
fmt.Println("Server Start Socket Error !", err)
os.Exit(1)
}
// 创建二进制协议
transportFactory := thrift.NewTFramedTransportFactory(thrift.NewTTransportFactory())
protocolFactory := thrift.NewTBinaryProtocolFactoryDefault()
// 创建Processor,用一个端口处理多个服务
divmodProcessor := compute.NewDivModProcessor(new(divmodThrift))
mulrangeProcessor := compute.NewMulRangeProcessor(new(mulrangeThrift))
multiProcessor := thrift.NewTMultiplexedProcessor()
// 给每个service起一个名字
multiProcessor.RegisterProcessor("divmod", divmodProcessor)
multiProcessor.RegisterProcessor("mulrange", mulrangeProcessor)
// 启动服务器
server := thrift.NewTSimpleServer4(multiProcessor, serverTransport,
transportFactory, protocolFactory)
server.Serve()
// 退出时停止服务器
defer server.Stop()
}
|
启动服务器这里也有坑点,网上有文章说使用NewTSimpleServer2可以直接启用二进制格式的数据传输,这是错的。想用二进制或其他格式传输数据,必须明确生成对应的ProtocolFactory并使用NewTSimpleServer4创建服务器。官方给的例子就是这样做的,我也是在被反复坑了数次之后才确认了这个问题。
实现客户端
要建立客户端,也要按照如下几个步骤:
- 创建 Transport
- 创建 Protocol
- 基于 Potocol 创建 Client
- 打开 Transport(不一定要在client创建后才打开,但必须在protocol创建后,接口调用前打开)
- 调用接口
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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
|
import (
"context"
"fmt"
"git.apache.org/thrift.git/lib/go/thrift"
"net"
"os"
"yufa/demo/thrift_demo/compute"
)
func ThriftDemoRPCClient() {
// 先建立和服务器的连接的socket,再通过socket建立Transport
socket, err := thrift.NewTSocket(net.JoinHostPort("127.0.0.1", "8081"))
if err != nil {
fmt.Println("Error opening socket:", err)
os.Exit(1)
}
transport := thrift.NewTFramedTransport(socket)
// 创建二进制协议
protocol := thrift.NewTBinaryProtocolTransport(transport)
// 打开Transport,与服务器进行连接
if err := transport.Open(); err != nil {
_, _ = fmt.Fprintln(os.Stderr, "Error opening 127.0.0.1:8081 ", err)
os.Exit(1)
}
defer transport.Close()
// 接口需要context,以便在长操作时用户可以取消RPC调用
ctx := context.Background()
// 使用divmod服务
divmodProtocol := thrift.NewTMultiplexedProtocol(protocol, "divmod")
// 创建代理客户端,使用TMultiplexedProtocol访问对应的服务
c := thrift.NewTStandardClient(divmodProtocol, divmodProtocol)
client := compute.NewDivModClient(c)
res, err := client.DoDivMod(ctx, 100, 3)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
fmt.Println(res)
// 使用mulrange服务
// 步骤与上面的相同
mulProtocol := thrift.NewTMultiplexedProtocol(protocol, "mulrange")
c = thrift.NewTStandardClient(mulProtocol, mulProtocol)
client2 := compute.NewMulRangeClient(c)
num, err := client2.BigRange(ctx, 100)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
fmt.Println(num)
}
|
控制台中看到客户端执行结果:
Result_({Div:33 Mod:1})
933262154439441526816992388562667004907159682643816214685929638952175999932299156089414639761565
18286253697920827223758251185210916864000000000000000000000000
(完)