Thrift

Thrift是一个轻量级跨语言远程服务调用框架,最初由Facebook开发,后面进入Apache开源项目。它通过自身的IDL中间语言, 并借助代码生成引擎生成各种主流语言的RPC服务端/客户端模板代码。

Thrift支持多种不同的编程语言,包括C++JavaPythonPHPRuby等,本系列主要讲述基于Java语言的Thrift的配置方式和具体使用。

Thrift 技术栈

Thrift软件栈的定义非常的清晰, 使得各个组件能够松散的耦合, 针对不同的应用场景, 选择不同是方式去搭建服务。

img

Thrift软件栈分层从下向上分别为:传输层(Transport Layer)、协议层(Protocol Layer)、处理层(Processor Layer)和服务层(Server Layer)。

  • 传输层(Transport Layer):传输层负责直接从网络中读取写入数据,它定义了具体的网络传输协议;比如说TCP/IP传输等。
  • 协议层(Protocol Layer):协议层定义了数据传输格式,负责网络传输数据的序列化反序列化;比如说JSONXML二进制数据等。
  • 处理层(Processor Layer):处理层是由具体的IDL接口描述语言)生成的,封装了具体的底层网络传输序列化方式,并委托给用户实现的Handler进行处理。
  • 服务层(Server Layer):整合上述组件,提供具体的网络线程/IO服务模型,形成最终的服务。

Thrift的特性

(一) 开发速度快

通过编写RPC接口Thrift IDL文件,利用编译生成器自动生成服务端骨架(Skeletons)和客户端桩(Stubs)。从而省去开发者自定义维护接口编解码消息传输服务器多线程模型等基础工作。

  • 服务端:只需要按照服务骨架接口,编写好具体的业务处理程序(Handler)即实现类即可。
  • 客户端:只需要拷贝IDL定义好的客户端桩服务对象,然后就像调用本地对象的方法一样调用远端服务。

(二) 接口维护简单

通过维护Thrift格式的IDL(接口描述语言)文件(注意写好注释),即可作为给Client使用的接口文档使用,也自动生成接口代码,始终保持代码和文档的一致性。且Thrift协议可灵活支持接口可扩展性

(三) 学习成本低

因为其来自Google Protobuf开发团队,所以其IDL文件风格类似Google Protobuf,且更加易读易懂;特别是RPC服务接口的风格就像写一个面向对象Class一样简单。

初学者只需参照:thrift.apache.org/,一个多小时就可以理解Thrift IDL文件的语法使用。

(四) 多语言/跨语言支持

Thrift支持C++JavaPythonPHPRubyErlangPerlHaskellC#CocoaJavaScriptNode.jsSmalltalk等多种语言,即可生成上述语言的服务器端客户端程序

对于我们经常使用的JavaPHPPythonC++支持良好,虽然对iOS环境的Objective-C(Cocoa)支持稍逊,但也完全满足我们的使用要求。

(五) 稳定/广泛使用

Thrift在很多开源项目中已经被验证是稳定高效的,例如CassandraHadoopHBase等;国外在Facebook中有广泛使用,国内包括百度、美团小米、和饿了么等公司。

对比HTTP

和 http 相比,同属于应用层,走 tcp 协议。Thrift 优势在于发送同样的数据,request包 和 response包 要比 http 小很多,在整体性能上要优于 http 。

Thrift学习

如果使用thrift框架来调用别人的服务,则必须要知道别人写的.thrift文件.
.thrift文件,其实就是你要访问的服务的一份说明书,里面会定义服务的名称,以及该服务中的函数,以及函数的入参有几个,每个参数的数据类型是什么,参数的名称是啥,还有返回值的数据类型。

Thrift实例

以python+thrift为例

环境准备:

  1. 首先使用 thrift 之前需要定义一个 .thrift 格式的文件,比如 test.thrift
1
2
3
4
service Transmit {
string sayMsg(1:string msg);
string invoke(1:i32 cmd 2:string token 3:string data)
}
  1. 然后运行命令:thrift-0.9.3.exe -gen py test.thrift 生成 python 代码
1
thrift-0.9.3.exe -gen py test.thrift

img

  1. 然后将生成的 python 代码 和 文件,放到新建的 python 项目中。完成后先运行服务器代码,启动服务
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
import json
from test import Transmit
from test.ttypes import *
from thrift.transport import TSocket
from thrift.transport import TTransport
from thrift.protocol import TBinaryProtocol
from thrift.server import TServer
import socket


class TransmitHandler:
def __init__(self):
self.log = {}

def sayMsg(self, msg):
msg = json.loads(msg)
print("sayMsg(" + msg + ")")
return "say " + msg + " from " + socket.gethostbyname(socket.gethostname())

def invoke(self,cmd,token,data):
cmd = cmd
token =token
data = data
if cmd ==1:
return json.dumps({token:data})
else:
return 'cmd不匹配'

if __name__=="__main__":
handler = TransmitHandler()
processor = Transmit.Processor(handler)
transport = TSocket.TServerSocket('127.0.0.1', 8000)
tfactory = TTransport.TBufferedTransportFactory()
pfactory = TBinaryProtocol.TBinaryProtocolFactory()
server = TServer.TSimpleServer(processor, transport, tfactory, pfactory)
print("Starting python server...")
server.serve()
  1. 客户端代码调用服务,以python调用为例
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
import sys
import jsonfrom test import Transmit
from test.ttypes import *
from test.constants import *
from thrift import Thrift
from thrift.transport import TSocket
from thrift.transport import TTransport
from thrift.protocol import TBinaryProtocol


transport = TSocket.TSocket('127.0.0.1', 8000)
transport = TTransport.TBufferedTransport(transport)
protocol = TBinaryProtocol.TBinaryProtocol(transport)
client = Transmit.Client(protocol)
# Connect!
transport.open()

cmd = 2
token = '1111-2222-3333-4444'
data = json.dumps({"name":"zhoujielun"})
msg = client.invoke(cmd,token,data)
print(msg)
transport.close()

# 执行结果:cmd不匹配

参考

  • 转载
  1. Apache Thrift系列详解(一) - 概述与入门 - 掘金 (juejin.cn)
  2. python 使用 thrift 教程 - 三只松鼠 - 博客园 (cnblogs.com)
  3. python:rpc框架之thrift框架 - 掘金 (juejin.cn)