vlambda博客
学习文章列表

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环境

Thrift IDL

基本类型

byte: 有符号字节

i16: 16位有符号整数

i32: 32位有符号整数

i64: 64位有符号整数

double: 64位浮点数

string: 字符串

容器类型

list<T>set<T>map<K, V>

Java中的集合类型listsetmap一样。

结构体(struct)

struct的定义形式如下:

struct UserInfo {

    1:required i32 uid,

    2:required string name,

    3:required i8 sex,

    4:required i16 age,

}

C语言中的结构体类似。

枚举(enum)

枚举的定义形式和JavaEnum定义差不多

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++中的includeJava中的import。使用关键字include定义,例如:

include "global.thrift"

注释

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

可选与必选

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

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

# 定义命名空间namespace py Services.UserService # 定义枚举类型enum ErrCodeEnum { SERVER_ERROR = 10001, PARAM_ERROR = 20001,} # 定义结构体struct UserListReq { 1:required list<i32> uidList;} struct UserInfo { 1:required i32 uid, 2:required string name, 3:required i8 sex, 4:required i16 age, 5:optional string nick = '',} struct UserListResp { 1:required list<UserInfo> lists;} # 定义异常exception ApiErrorException { 1:ErrCodeEnum errCode; 2:string errMsg;} # 定义服务service UserService{    # 获取用户列表   UserListResp userList(1: UserListReq req) throws (1: ApiErrorExceptione)}

 

生成Thrift文件

thrift -r -gen py UserService.thrift

生成的thrift对应python模板文件结构如下:

Python服务端\客户端实现

服务端代码:

# -*- coding: UTF-8 -*-import syssys.path.append('gen-py') from Services.UserService import UserServicefrom Services.UserService.ttypes importErrCodeEnum, UserInfo, UserListResp, ApiErrorException from thrift.transport import TSocketfrom thrift.transport import TTransportfrom thrift.protocol import TBinaryProtocolfrom 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 syssys.path.append('gen-py') from Services.UserService import UserServicefrom Services.UserService.ttypes importUserListReq, UserListResp, UserInfo, ApiErrorException from thrift import Thriftfrom thrift.transport import TSocketfrom thrift.transport import TTransportfrom 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