Thrift远程过程调用
Thrift是一个轻量级、跨语言的远程服务调用(RPC)框架,由Facebook开发后在Apache开源。Thrift通过编写.thrift文件,借助Thrift代码生成引擎生成各种主流语言的客户端、服务端RPC模板代码。常用的语言比如C++,Java, Python, PHP, Ruby, Erlang, Perl, Haskell, C#, Cocoa, JavaScript, Node.js,Smalltalk, and OCaml都支持。(本篇用基于python描述Thrift的配置及使用)
RPC概述
RPC,全称为Remote Procedure Call,即远程过程调用,它是一个计算机通信协议。它允许像调用本地服务一样调用远程服务,RPC采用客户机/服务器模式。请求程序就是一个客户机,而服务提供程序就是一个服务器。简单来讲,就是我们从一台机器(客户端)传递参数调用另外一个机器(服务端)的函数或方法(或称为服务)并得到返回结果,通过自定义的传输协议和数据协议来实现RPC,从这个层面来讲,HTTP也可以算做一种RPC的实现。调用过程可用下图表示:
Thrift环境安装
为了快速构建Thrift环境,我们可以拉取Thrift官方镜像。
#拉取thrift镜像
docker pull thrift
#查看下载镜像
docker images
#确认thrift环境
Thrift IDL
基本类型
byte: 有符号字节
i16: 16位有符号整数
i32: 32位有符号整数
i64: 64位有符号整数
double: 64位浮点数
string: 字符串
容器类型
list<T>、set<T>、map<K, V>
和Java中的集合类型list、set、map一样。
结构体(struct)
struct的定义形式如下:
struct UserInfo {
1:required i32 uid,
2:required string name,
3:required i8 sex,
4:required i16 age,
}
和C语言中的结构体类似。
枚举(enum)
枚举的定义形式和Java的Enum定义差不多
enum ErrCodeEnum {
SERVER_ERROR = 10001,
PARAM_ERROR = 20001,
}
异常(exception)
thrift支持自定义exception,规则和struct一样,如下:
exception ApiErrorException {
1:ErrCodeEnum errCode;
2:string errMsg;
}
服务(service)
thrift定义服务相当于Java中创建Interface一样,创建的service经过代码生成命令之后就会生成客户端和服务端的框架代码。定义形式如下:
service UserService{
UserListResp userList(1: UserListReq req) throws(1: ApiErrorException e)
}
类型定义
thrift支持类似C++一样的typedef定义,比如:
typedef i32 Integer
typedef i64 Long
常量(const)
thrift也支持常量定义,使用const关键字,例如:
const i32 MAX_VALUE = 100
命名空间
thrift的命名空间相当于Java中的package的意思,主要目的是组织代码。thrift使用关键字namespace定义命名空间,例如:
namespace py Services.UserService
格式是:namespace语言名路径 (注意末尾不能有分号)
文件包含
thrift也支持文件包含,相当于C/C++中的include,Java中的import。使用关键字include定义,例如:
include "global.thrift"
注释
thrift注释方式支持shell风格的注释,支持C/C++风格的注释,即#和//开头的语句都单当做注释,/**/包裹的语句也是注释。
可选与必选
thrift提供两个关键字required,optional,分别用于表示对应的字段时必填的还是可选的。例如:
struct UserInfo {
1:required i32 uid,
2:required string name,
3:required i8 sex,
4:required i16 age,
5:optional string nick = '',
}
编写Thrift定义文件UserService.thrift
# 定义命名空间
py Services.UserService
# 定义枚举类型
enum ErrCodeEnum {
10001, =
PARAM_ERROR = 20001,
}
# 定义结构体
UserListReq {
1:required list<i32> uidList;
}
UserInfo {
1:required i32 uid,
2:required string name,
3:required i8 sex,
4:required i16 age,
5:optional string nick = '',
}
UserListResp {
1:required list<UserInfo> lists;
}
# 定义异常
exception ApiErrorException {
1:ErrCodeEnum errCode;
2:string errMsg;
}
# 定义服务
service UserService{
# 获取用户列表
UserListReq req) throws (1: ApiErrorExceptione) :
}
生成Thrift文件
thrift -r -gen py UserService.thrift
生成的thrift对应python模板文件结构如下:
Python服务端\客户端实现
服务端代码:
# -*- coding: UTF-8 -*-
import sys
sys.path.append('gen-py')
from Services.UserService import UserService
from Services.UserService.ttypes importErrCodeEnum, UserInfo, UserListResp, ApiErrorException
from thrift.transport import TSocket
from thrift.transport import TTransport
from thrift.protocol import TBinaryProtocol
from thrift.server import TServer
class UserInfoHandler:
def__init__(self):
pass
defget_user_by_uid_list(self, uid_list):
all_user = {
1: {
'uid':1,
'name': 'NO1',
'sex':1,
'age':18,
'nick': 'nick1R===x'
},
2: {
'uid':2,
'name': 'NO2',
'sex':2,
'age':19,
'nick': 'nick2'
},
}
user_list = []
for uid in uid_list:
if uid in all_user:
user = all_user[uid]
user_info = UserInfo(user['uid'], user['name'], user['sex'],user['age'], user['nick'])
user_list.append(user_info)
return user_list
defuserList(self, req):
try:
uid_list = req.uidList
if not uid_list:
raise Exception('参数错误',ErrCodeEnum.PARAM_ERROR)
user_list_all = self.get_user_by_uid_list(uid_list)
if not user_list_all:
raise Exception('服务器错误',ErrCodeEnum.SERVER_ERROR)
user_list_resp = UserListResp(user_list_all)
print('服务调用完成')
return user_list_resp
except Exception as e:
print(e)
raise ApiErrorException(e.getCode(), e.getMessage())
if __name__ == '__main__':
handler = UserInfoHandler()
processor = UserService.Processor(handler)
transport = TSocket.TServerSocket(host='127.0.0.1', port=9090)
tfactory = TTransport.TBufferedTransportFactory()
pfactory = TBinaryProtocol.TBinaryProtocolFactory()
# 简单服务器模式
server = TServer.TSimpleServer(processor, transport, tfactory, pfactory)
print("Starting thrift server in python...")
server.serve()
print("done!")
客户端代码:
# -*- coding: UTF-8 -*-
import sys
sys.path.append('gen-py')
from Services.UserService import UserService
from Services.UserService.ttypes importUserListReq, UserListResp, UserInfo, ApiErrorException
from thrift import Thrift
from thrift.transport import TSocket
from thrift.transport import TTransport
from thrift.protocol import TBinaryProtocol
def main():
try:
transport = TSocket.TSocket('localhost', 9090)
transport = TTransport.TBufferedTransport(transport)
protocol = TBinaryProtocol.TBinaryProtocol(transport)
client = UserService.Client(protocol)
#Connect
transport.open()
#request
req = UserListReq()
req.uidList = [1, 2]
result = client.userList(req)
print(result)
#Close
transport.close()
except Thrift.TException as e:
print(e)
except ApiErrorException as e:
print(e)
if __name__ == '__main__':
main()
#启动服务端代码
python service.py
#客户端请求并返回结果
python clicent.py
Ref:
https://www.jianshu.com/p/0f4113d6ec4b
https://zhuanlan.zhihu.com/p/45194118
https://zhuanlan.zhihu.com/p/31952319
https://zhuanlan.zhihu.com/p/22934974
https://www.jianshu.com/p/643f1e5157a4