RPC-Thrift-python

6 篇文章 0 订阅
3 篇文章 0 订阅

共包含5部分

一. RPC与http比较

二. RPC开源框架thrift

三. 安装thrift,及python样例代码

四. thrift-python 常用类剖析

五. 生产环境使用样例

 

一. RPC与http比较

RPC过程:

RPC是一种C/S架构的服务模型,server端提供接口供client调用,client端向server端发送数据,server端接收client端的数据进行相关计算并将结果返回给client端。

 

rpc与http比较:

传输效率:

  • RPC,使用自定义的TCP协议,可以让请求报文体积更小,或者使用HTTP2协议,也可以很好的减少报文的体积,提高传输效率

  • HTTP,如果是基于HTTP1.1的协议,请求中会包含很多无用的内容,如果是基于HTTP2.0,那么简单的封装以下是可以作为一个RPC来使用的,这时标准RPC框架更多的是服务治理

性能消耗,主要在于序列化和反序列化的耗时

  • RPC,可以基于thrift实现高效的二进制传输

  • HTTP,大部分是通过json来实现的,字节大小和序列化耗时都比thrift要更消耗性能

 

 

二. RPC开源框架 Thrift:

Thrift通过一个中间语言(IDL, 接口定义语言)来定义RPC的接口和数据类型,然后通过一个编译器生成不同语言的代码,并由生成的代码负责RPC协议层和传输层的实现。

Thrift实际上是实现了C/S模式,通过代码生成工具将接口定义文件生成服务器端和客户端代码(可以为不同语言),从而实现服务端和客户端跨语言的支持。用户在Thirft描述文件中声明自己的服务,这些服务经过编译后会生成相应语言的代码文件,然后用户实现服务(客户端调用服务,服务器端提服务)便可以了。其中protocol(协议层, 定义数据传输格式,可以为二进制或者XML等)和transport(传输层,定义数据传输方式,可以为TCP/IP传输,内存共享或者文件共享等)被用作运行时库。

 

Thrift交互流程:

thriftæ¶æ

1.在Client和Server的最顶层都是用户自定义的处理逻辑,也就是说用户只需要编写用户逻辑,就可以完成整套的RPC调用流程。用户逻辑的下一层是Thrift自动生成的代码,这些代码主要用于结构化数据的解析,发送和接收,同时服务器端的自动生成代码中还包含了RPC请求的转发

2.TTransport负责以字节流方式发送和接收Message,是底层IO模块在Thrift框架中的实现,每一个底层IO模块都会有一个对应TTransport来负责Thrift的字节流(Byte Stream)数据在该IO模块上的传输。例如TSocket对应Socket传输,TFileTransport对应文件传输。

3.TProtocol主要负责结构化数据组装成Message,或者从Message结构中读出结构化数据。TProtocol将一个有类型的数据转化为字节流以交给TTransport进行传输,或者从TTransport中读取一定长度的字节数据转化为特定类型的数据。

4.TServer负责接收Client的请求,并将请求转发到Processor进行处理。TServer主要任务就是高效的接受Client的请求,特别是在高并发请求的情况下快速完成请求。

5.Processor(或者TProcessor)负责对Client的请求做出响应,包括RPC请求转发,调用参数解析和用户逻辑调用,返回值写回等处理步骤。Processor是服务器端从Thrift框架转入用户逻辑的关键流程。Processor同时也负责向Message结构中写入数据或者读出数据。

利用Thrift用户只需要做三件事:

1) 利用IDL定义数据结构及服务

2) 利用代码生成工具将(1)中的IDL编译成对应语言(如PYTHON),编译后得到基本的框架代码

3) 在(2)中框架代码基础上完成完整代码

 

三.安装thrift,及python示例

centos环境安装thrift

1.下载boost https://www.boost.org/

./bootstrap.sh

./b2 install

 

2.下载thrift http://thrift.apache.org/download

./configure

make

make install

 

使用thrift,python示例

1.编写IDL   

test_thrift.thrift

样例:

typedef i32 int

struct pair {
    1: string str1,
    2: string str2
}

service test_thrift

{
    int test_api(
    1:int n1,
    2:int n2)

    int test_pair(
    1: pair n1
    )

    pair test_pair2(
    1: int n1
    )
}

 

2.编译:thrift -gen py test_thrift.thrift

会在同目录下生成 gen-py 目录,目录下test_thrift文件夹为编译好的调用库

-out可将gen-py替换为指定目录,需要先建立好目录

例:thrift -gen py -out thrift_gen eslib_rpc.thrift

3.服务器端样例代码:

server.py:

#!/usr/bin/env python
# -*- coding:utf-8 -*-

from test_thrift import test_thrift
from test_thrift.ttypes import *
from thrift.transport import TSocket
from thrift.transport import TTransport
from thrift.protocol import TBinaryProtocol
from thrift.server import TServer


class RpcServerHandler(object):

    def test_api(self, n1, n2):
        print 'test_api'
        print n1, n2
        return n1 + n2

    def test_pair(self, n1):
        print 'test_pair'
        print n1
        return int(n1.str1)

    def test_pair2(self, n1):
        print 'test_pair2'
        print n1
        result = pair('100', '200')
        print result
        return result


if __name__ == '__main__':
    handler = RpcServerHandler()
    processor = test_thrift.Processor(handler)
    transport = TSocket.TServerSocket(port=9080)
    tfactory = TTransport.TBufferedTransportFactory()
    pfactory1 = TBinaryProtocol.TBinaryProtocolFactory()
    server_bin = TServer.TSimpleServer(processor, transport, tfactory, pfactory1)
    print('Starting the server...')
    server_bin.serve()

    print('done.')

4.客户端样例代码

#!/usr/bin/env python
# encoding: utf-8
from thrift.transport import TTransport
from thrift.transport import TSocket
from thrift.protocol import TBinaryProtocol
from test_thrift import test_thrift
from test_thrift.ttypes import pair


def rpc_client():
    transport = TSocket.TSocket("127.0.0.1", 9080)
    transport = TTransport.TBufferedTransport(transport)
    protocol = TBinaryProtocol.TBinaryProtocol(transport)
    client = test_thrift.Client(protocol)
    transport.open()
    return transport, client

transport, client = rpc_client()
result = client.test_api(1, 2)
print result

new_pair = pair('10', '20')
result = client.test_pair(new_pair)
print result

result = client.test_pair2(1)
print result

transport.close()

 

四.thrift-python 常用类剖析

TServer

主要任务是接收Client的请求,并转到某个TProcessor上进行请求处理,针对不同的访问规模,Thrift提供了不同的TServer模型:

1. TSimpleServer:使用阻塞IO的单线程服务器

2. TThreadedServer:使用阻塞IO的多线程服务器,每一个请求都在一个线程里处理,并发访问情况下会有很多线程同时在运行

3. TThreadPoolServer:使用阻塞IO的多线程服务器,使用线程池处理线程

4. TNonBlockingServer: 使用非阻塞IO的多线程服务器,使用少量线程即可完成大并发量的请求响应,比如使用TFramedTransport

Thrift使用livevent作为服务的事件驱动器,libevent就是epoll更高级的封装,处理大量请求的话,主要在TThreadedServer与TNonblockingServer中进行选择。TNonblockingServer能够使用少量线程处理大量并发连接,但是延迟较高,TThreadedServer的延迟较低。实际中,TThreadedServer的吞吐量可能会比TNonblockingServer高,但是TThreadedServer的CPU占用要高很多

 

TTransport

Thrift最底层的传出可以使用socket,File,Zip来实现,TTransport是与底层数据传输紧密相关的传输层,每一种支持的底层传输方式都存在对应的TTransport。在TTransport这一层,数据按字节流方式处理,即传输层看到的是一个个字节,并把这些字节按照顺序发送和接收。TTransport并不了解它传输的数据是什么类型,实际上传输层也不关心,只需要按照字节方式对数据进行发送和接收即可。数据类型的解析在TProtocol这一层完成。

TTransport有以下几个类:

1. TSocket: 使用阻塞的TCP Socket进行数据传输

2. THttpTransport:采用Http传输协议进行数据传输

3. TFileTransport:文件、日志传输类,允许client将文件传给server,允许server将收到的数据写到文件中

4.TZlibTransport:与其他的TTransport配合使用,压缩后对数据进行传输,或者将收到的数据解压

 

TProtocol

主要任务是把TTransport中的字节流转化成数据流,在TProtocol这一层就会出现具有数据类型的数据,如整形,浮点数,字符串,结构体等,TProtocol只会按照指定类型将数据读出和写入,和数据的真正用途,在Thrift自动生成的Server和Client中处理

Thrift可以让用户选客户端与服务端之间传输通信协议的类别,在传输协议上总体划分为文本text和二进制binnary,为了节约带宽,提高传输效率,一般情况下使用二进制类型的传输协议居多

常用协议如下:

1. TBinaryProtocol:二进制格式

2. TComactProtocol:高效率的、密集的二进制编码格式

3. TJSONProtocol:使用JSON的数据编码协议进行数据传输

4. TSimpleJSONProtocol:提供JSON只写协议,生成的文件很容易通过脚本语言解析

5. TDebugProtocol: 使用易懂的可读文本格式,便于debug

 

TCompactProtocol 高效的编码方式,使用了类似于ProtocolBuffer的Variable-Length Quantity (VLQ) 编码方式,主要思路是对整数采用可变长度,同时尽量利用没有使用Bit。对于一个int32并不保证一定是4个字节编码,实际中可能是1个字节,也可能是5个字节,但最多是五个字节。TCompactProtocol并不保证一定是最优的,但多数情况下都会比TBinaryProtocol性能要更好。

 

TProcessor/Processor

主要对TServer中的一次请求的InputProtocol和OutputProtcol中写入用户逻辑的返回值

Processor是TServer从Thrift框架转到用户逻辑的关键流程,同时Client所有的RPC调用都会经过TProcessor.process处理并转发

TProcessor对于一次RPC调用的处理过程:

1.TServer接收到RPC请求后,调用TProcessor.process进行处理

2.TProcessor.process首先调用TTransport.readMessageBegin接口,读出RPC调用的名称和RPC调用的类型,如果RPC调用类型是RPC Call,则调用TProcessor.process_fn继续处理,对于未知的RPC调用类型,则抛出异常

3. TProcessor.process_fn根据RPC调用名称到自己的processMap中查找对应的RPC处理函数,对于存在的RPC处理函数,调用处理函数继续进行请求响应,不存在抛异常

4. PRC处理函数调用RPC请求参数的解析类,从TProtocol中读入数据完成参数解析,将参数放到一个Struct中,Thrift会检查参数字段id和类型是否匹配,对于不符合要求的参数都会跳过。这样,RPC接口发生变化之后,旧的处理函数在不做修改的情况下,可以通过跳过不认识的参数,继续提供服务,进而在RPC框架中提供接口的多Version支持

5.完成参数解析后,调用用户逻辑,完成真正的请求响应

6.用户逻辑的返回值使用返回值打包类进行打包,写入TProtocol

 

ThriftClient

ThriftClient跟TProcessor一样,主要操作InputProtocol和OutputProtocol

1. send,将用户的调用参数作为一个整体的Struct写入TProcotol,并发送到TServer

2. send结束后,ThriftClient立刻进入Receive状态等待TServer的响应,对应TServer返回的响应,使用返回值解析类进行返回值解析

 

五. 生产环境使用样例

背景:analysisOpenApi为单独的web服务,eslib为服务中一个子模块(调用方法为直接引入调用函数),现将eslib拆分出来成为rpc调用的子服务

 

1.编写IDL

IDL文件:eslib_rpc.thrift

struct eslib_struct{
    1: string items_html,
    2: string items_qs_ans,
    3: string items_qs_exp
} # 定义rpc调用时的指定参数类型

service eslib_rpc_interface  # 定义rpc服务调用接口
{
    eslib_struct get_items_render_result(
    1: string items_info,
    2: string subject
    ), # 返回结构为上面定义的eslib_struct,传入参数为两个string

    string render_items(1: string item_ids, 2: string subject, 3: bool add_answer, 4: string start), # 返回结构为string
}

2.执行thrift

①在analysisOpenApi(需要调用rpc服务的客户端)项目下创建文件夹(thrift_gen)用来存储thrift生成的文件

②在analysisOpenApi项目下执行:thrift -gen py -out thrift_gen eslib_rpc.thrift

  会在创建的thrift_gen下生成:

  其中子目录名eslib_rpc对应执行的thrift文件的名称,eslib_rpc_interface.py文件中为thrift文件中service中定义的各个接口及client,ttypes.py中为thrift文件中定义的数据结构(eslib_struct)

在analysisOpenApi项目下编写client:(约定rpc客户端与服务端使用9080端口交互)

#!/usr/bin/env python
# encoding: utf-8
from thrift.transport import TTransport
from thrift.transport import TSocket
from thrift.protocol import TBinaryProtocol
from thrift_gen.eslib_rpc import eslib_rpc
from thrift_gen.eslib_rpc import ttypes


def rpc_client(): # 创建rpc客户端
    transport = TSocket.TSocket("127.0.0.1", 9080)
    transport = TTransport.TBufferedTransport(transport)
    protocol = TBinaryProtocol.TBinaryProtocol(transport)
    client = eslib_rpc.Client(protocol)
    transport.open()
    return transport, client


def get_items_render_result(items_info, subject): # 接口调用
    transport, client = rpc_client()
    result = client.get_items_render_result(items_info, subject)
    transport.close()
    return result


def render_items(item_ids, subject, add_answer, start): # 接口调用
    # item_ids json([str])
    # start = json([int])
    transport, client = rpc_client()
    result = client.render_items(item_ids, subject, add_answer, start)
    transport.close()
    return result


④  在eslib项目下(rpc服务端)执行①②,会在eslib下生成同样的目录文件

⑤ 在eslib项目下编写service:

#!/usr/bin/env python
# -*- coding:utf-8 -*-

"""
thrift:
thrift -gen py -out thrift_gen eslib_rpc.thrift

"""
from thrift_gen.eslib_rpc import eslib_rpc
from thrift_gen.eslib_rpc.ttypes import *
from thrift.transport import TSocket
from thrift.transport import TTransport
from thrift.protocol import TBinaryProtocol
from thrift.server import TServer
import time
import config
import json
import argparse
import logging

class EslibHandler(object):



    def get_items_render_result(self, items_info, subject):
        pass
        # 业务逻辑
        return None # return 结果为thrift中定义的接口返回格式 

    def def render_items(self, item_ids, subject, add_answer, start):
        pass
        # 业务逻辑
        return None # return 结果为thrift中定义的接口返回格式 


def rpc_instance(): # 初始化rpc服务
    handler = EslibHandler() # 定义接受调用的handler
    processor = eslib_rpc.Processor(handler)
    transport = TSocket.TServerSocket(port=config.get('rpc_port'))
    tfactory = TTransport.TBufferedTransportFactory()
    pfactory = TBinaryProtocol.TBinaryProtocolFactory()
    server = TServer.TThreadPoolServer(processor, transport, tfactory, pfactory)
    return server


if __name__ == '__main__':  # 启动rpc服务
    argp = argparse.ArgumentParser()
    argp.add_argument('--rpc_port', default=9080, type=int)
    args = argp.parse_args()
    config.load_config('server.conf')
    config.update('rpc_port', args.rpc_port)
    rpc_server = rpc_instance()
    logging.basicConfig(level=logging.DEBUG,
                        format='[%(levelname)1.1s %(asctime)s %(module)s:%(lineno)d] %(message)s',
                        datefmt='%d-%m-%Y %H:%M:%S')
    logging.info('{} Starting the server... {}'.format(time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()),
                                                       config.get('rpc_port')))
    rpc_server.serve()
    logging.info('done.')

启动eslib服务即可,在analysisOpenApi客户端即可通过rpc接口调用eslib

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值