【python】rpc框架之thrift框架

2,270 阅读6分钟

1/thrift简介

thrift最初是由facebook公司开发的开源的,跨平台的,跨语言的rpc框架。
(rpc框架是一个统称,包括很多具体的框架,比如谷歌开发的grpc框架,facebook开发的thrift框架)

现在facebook公司已经把thrift框架贡献给了apache基金会。

2/安装thrift

如果你是在mac上安装,可以使用命令 brew install thrift 来安装。
(前提是你已经安装了homebrew这个mac特有的包管理工具了)
如果你没有安装brew,当你brew install thrift的时候,会提示:
     command not found: brew,

则你需要先安装homebrew。
如何安装homebrew,可以参考:
https://juejin.cn/post/6982513337716178980/

<1>安装thrift

brew install thrift
这个安装过程的时间可能会比较长,请耐心等待。
默认是安装在/usr/local/Cellar目录下的(brew安装的东西,默认都是安装在Cellar这个目录下的)

<2>配置环境变量(把thrift的bin目录下的可执行文件加入到环境变量)

vim .bash_profile
export PATH=$PATH:/usr/local/Cellar/thrift/0.16.0/bin  
#把bin目录下的所有可执行文件加入到环境变量中,以后就可以直接使用thrift来执行命令了,不用加上绝对路径。
# 其实通过brew安装的包(工具)是不用再设置环境变量了

source .bash_profile
这样thrift的环境变量就是配置好了,且立即生效了。

<3>查看thrift的版本,确定是否安装成功

thrift --version  或者thrift -version 都可以,及一个'-'或者两个'--'都是可以的。
如果显示thrift的版本号,则代表thrift已经安装完成了。

3/服务端

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

<1>编写.thrift文件,以及生成一个目录,该目录包含所依赖的东西

如果是你自己要开发一个服务供别人来调用,那么你需要写一个.thrift文件
   .thrift文件是用IDL语言来写的,用//进行注释
   下面以evasion.thrift文件为例(evasion是逃单的意思)
    // .thrift文件就是你所要访问的服务的一份说明
    // service是关键字,是用来构建服务的。
    // Evasion是该服务的名称,你要访问的服务叫什么名字,这里你就写什么名字。
    // 前面的这个string是返回值的类型
    // hello_x是该服务中的一个函数,也就是接口,后面的(1:string what),分别是传入的参数的个数,类型,参数的名称
    // 如果是多个入参,可以写成(1:string s1, 2:string s2...,3 :i64 n1) 用逗号隔开
    // 一个服务中可以多个函数,中间用逗号隔开
    // 一个函数可以有多个入参,中间用逗号隔开
    
    namespace py dependence
    // namespace提供了一种组织代码的方式。其实就是,生成的文件放在:dependents这个文件夹下。
    // 这个文件夹的名字是可以随便写的,只要你觉得ok就行。
    // 当然这里的名字是啥,相应的server.py和client.py中就要对应好了。
    // py就是生成.py代码的包,,如果是 namespace java dependents 则就是生成.java代码的包
 
    service Evasion {
       string say_hello(1:string what),
       double cal_sum_value(1:i64 n1, 2:i64 n2),
       double similarity(1:string chat1, 2:string chat2)

    }
编写完evasion.thrift文件之后,运行thrift --gen py evasion.thrift 或者 thrift -gen py evasion.thrift ,这2条命令都可以,-gen 或者 --gen 都可以
会在evasion.thrift文件的同一级目录下生成一个gen-py的目录,该目录下还有别的层级。
具体是什么层级,和你.thrift文件中的namespace的设置有关。
然后对gen-py目录进行重命名(当然也可以不重命名,只要你能分清楚即可)

<2>服务端开发

以下是python开发服务的方式
# coding= utf-8

import sys
sys.path.append('./gen-py')

from thrift.transport import TSocket
from thrift.protocol import TBinaryProtocol
from thrift.server import TServer

from dependence.Evasion import * 
from dependence.ttypes import *


# 下面是该服务的主要功能
# thrift支持的数据类型有 'bool' | 'byte' | 'i8' | 'i16' | 'i32' | 'i64' | 'double' | # # 'string' | 'binary' | 'slist'(deprecated)
#list | map | set

# 下面的这个类class,就是要具体的去实现接口文件中定义的功能函数
class EvasionHandler:
    def say_hello(self, what):
        print('say_hello is called......,param is {}'.format(what))
        return "hello %s" % what
        
    def cal_sum_value(self, n1, n2):
        print('cal_sum_value is called......,param is {} and {}'.format(n1, ne2))
        if n1 >= n2:
            return n1
        else:
            return n2
            
    def similarity(self, chat1, chat2):
        print("similarity is called......, param is {} and {}".format(chat1, chat2))
        result = xxx    # 这里假装进行了负责的运算
        return result
        

def func_server_start():
    # 创建服务器
    handler = HelloHandler() # 实例化一个服务对象
    processor = Processor(handler)

    # 监听端口
    transport = TSocket.TServerSocket('localhost', 9234)

    # 选择传输层
    pfactory = TBinaryProtocol.TBinaryProtocolFactory()

    # 选择传输协议
    tfactory = TTransport.TBufferedTransportFactory()

    # 把上面定义的这些东西,都作为初始化的参数,实例化一个server对象
    server = TServer.TThreadPoolServer(processor,
                                       transport,
                                       tfactory,
                                       pfactory)

    # 给该server,设置一个线程数
    server.setNumThreads(5)
    print("server is running。。。。。")
    server.serve()


if __name__ == '__main__':
    func_server_start()  # 启动该服务,端口一直处于监听的状态,并且设置的最大的线程数量

4/客户端

在客户端,如果想调用远程的服务
需要先把别人的.thrift文件拿到,然后通过thrift --gen py xxx.thrift命令在本地生成python的代码。
# coding= utf-8

from thrift import Thrift
from thrift.transport import TSocket, TTransport
from thrift.protocol import TBinaryProtocol

import sys
sys.path.append('./gen-py')
from dependents.HelloWhat import *


def func_cli():
    try:
        transport = TSocket.TSocket('localhost', 9234)
        transport = TTransport.TBufferedTransport(transport)
        protocal = TBinaryProtocol.TBinaryProtocol(transport)

        # 创建一个客户端
        client = Client(protocal)

        transport.open()
        
        res1 = client.say_hello("thrift")
        res2 = client.cal_sum_value(10, 20)
        print(res1)
        print(res2)

        transport.close()
    
    # 如果调用远程的服务出现问题,,则捕捉异常
    except Exception as err:
        print(err)

if __name__ == "__main__":
    func_cli()

5/使用thriftpy

thriftpy这个第三方包(现在已经更新为thriftpy2)对thrift进行了封装,可以动态解析thrift的接口文件。

5.1/安装thriftpy2

pip install thriftpy2

5.2/编写IDL文件

pingpong.thrift
    service PingService {
        string ping(),
    }
    service AargsPingService {
        string ping(1:string ping);
    }
    service Sleep {
        oneway void sleep(1: i32 seconds)
    }

5.3/服务端开发

# coding=utf-8

import thriftpy2
from thriftpy2.rpc import make_server
pp_thrift = thriftpy2.load("pingpong.thrift", module_name="pp_thrift")

# 实现.thrift文件定义的接口
class Dispatcher(object):
    def ping(self):
        print("ping pong!")
        return 'pong'

def main():
    # 定义监听的端口和服务
    server = make_server(pp_thrift.PingService, Dispatcher(), '127.0.0.1', 6000)
    print("serving...")
    server.serve()
if __name__ == '__main__':
    main()

5.4/客户端开发

# coding=utf-8

import thriftpy2
#from thriftpy2.rpc import client_context
from thriftpy2.rpc import make_client
# 读入thrift文件,module_name最好与server端保持一致,也可以不保持一致
pp_thrift = thriftpy2.load("pingpong.thrift", module_name="pp_thrift")

def main():
    #with client_context(pp_thrift.PingService, '127.0.0.1', 6000) as c:
    #    pong = c.ping()
    #    print(pong)
    client = make_client(pp_thrift.PingService, '127.0.0.1', 6000)
    result = client.ping()
    print(result)
    
if __name__ == '__main__':
    main()