简单学习rpc -- thrift 远程调用流程简单分析

1 篇文章 0 订阅

Blog地址:https://www.jiangdog.com/blog/rpc-thrift-source-code

http://blog.csdn.net/jw690114549/article/details/70210998


了解RPC的流程

通过阅读你应该知道的 RPC 原理可以很清楚的了解到一次RPC调用的流程。 
1. 客户端client调用本地代码的方式调用服务代码(实际上应该是调用的服务代码在client的存根中的代码)。 
2. client stub接收到后将所调用的服务名,参数等编码成适合网络传输的消息体。 
3. client stub找到请求的服务地址,并将消息体发送给服务器。 
4. server stub接收到消息后对消息体进行反序列化。 
5. server stub通过解码后得到的服务名,参数等找到服务器本地的服务代码。 
6. 调用server服务端本地代码得到结果。 
7. server stub将得到的结果编码成消息体返回给client stub。 
8. client stub接收到结果消息体后再解码。 
9. client 得到最终结果。

可以发现如果是一个RPC框架需要:

  • 服务端server stub存根, 客户端 client stub存根(实际上是服务接口的存根实现)。
  • 解码,编码。包括了消息体的数据结构和序列化。
  • 通信。

简单分析thrift如何实现rpc

回顾一下一个简单thrift demo的实现流程。 
1. 利用IDL编写.thrift文件,其中应该定义了各个服务的接口(服务名,参数,返回值等)。 
2. 利用thrift compiler对.thrift文件进行编译,生成对应语言的服务接口文件(包括了客户端部分和服务端)。 
3. 服务端: 实现服务接口 -> 指定监听服务地址和端口,协议类,传输层 -> 开启监听 
4. 客户端: 制定请求服务地址和端口,协议类,传输层 -> 实例化服务接口的客户端client对象,调用本地存根实现的方法。

客户端通过接口文件中的客户端部分生成一个Client对象,这个客户端对象中包含所有接口函数的存根实现,然后用户代码就可以通过这个Client对象来调用thrift文件中的那些接口函数了

5.服务接口函数的存根将调用请求发送给服务器,thrift服务端利用接受到的调用的函数名和参数等,找到服务接口的具体实现,并返回给client对象。 
6. client对象再将返回值返回给用户。

thrift各部分代码

thrift实现rpc主要依靠的是以下几个部分: 
1. TTransport:传输类 
2. TProtocol: 协议类 
3. Processer:客户端执行类 
4. Client:客户端存根类 
5. TSocket和TServerSocket:封装了socket部分方法(实际也是传输类)。

直接看各部分的代码比较晦涩难懂,根据pingpong demo代码,简单分析整个流程:

pp_client.py:

def ping_client():
    try:
        # common
        tsocket = TSocket.TSocket('127.0.0.1', 8080)  # 通信socket并设置请求ip和端口
        transport = TTransport.TBufferedTransport(tsocket)  # 传输类型

        # single service
        protocol = TBinaryProtocol.TBinaryProtocol(transport)  # 通信协议二进制协议(与服务器端保持一致)
        pingpong_client = PingPong.Client(protocol)

        transport.open()  # 打开socket传输并建立连接

        print("The return value is : ")
        print(pingpong_client.ping())
        print("............")
        transport.close()
    except Thrift.TException as tx:
        print('%s' % tx.message)

如上述代码所示: 
1. pingpong_client = PingPong.Client(protocol)实例化生成的pingpong_client实际是包含了所有服务接口函数的存根实现,是由thrift compiler编译.thrift文件生成的。

class Iface(object):
    def ping(self):
        pass

class Client(Iface):
    def __init__(self, iprot, oprot=None):
        self._iprot = self._oprot = iprot
        if oprot is not None:
            self._oprot = oprot
        self._seqid = 0

    def ping(self):
        self.send_ping()
        return self.recv_ping()

    def send_ping(self):
        ...

    def recv_ping(self):
        ...
        TApplicationException(TApplicationException.MISSING_RESULT, "ping failed: unknown result")

可以发现Client继承了Iface,实现了ping()(即在.thrift文件定义的服务service接口中的方法)。PingPong.Client初始化需要iprotoprot参数(值得注意的是同时还是初始化了一个self._seqid=0),顾名思义即应该输入协议和输出协议,客户端和服务端协议必须相同,下述代码简单描述了,server端输入协议(client输出协议)是compact_pfactory(压缩协议),server端输出协议(client输入协议)是binary_protocol(二进制),具体之前的实例化过程参照demo。

# server
server = TServer.TSimpleServer(pingpong_processor, transport, tfactory, tfactory, compact_pfactory, binary_pfactory)

# client
pingpong_client = PingPong.Client(binary_protocol, compact_protocol)

至此一个client stub存根就被实例化好了,通过调用client stub其中的ping()来请求server端。 
2. 如1中Client所示,实际pingpong_client.ping()是调用了send_ping()来发送请求,recv_ping()来接收返回结果。

class Client(Iface):
    def ping(self):
        self.send_ping()
        return self.recv_ping()

    def send_ping(self):
        self._oprot.writeMessageBegin('ping', TMessageType.CALL, self._seqid)
        args = ping_args()
        args.write(self._oprot)
        self._oprot.writeMessageEnd()
        self._oprot.trans.flush()

    ...


class ping_args(object):

    thrift_spec = (
    )


    def write(self, oprot):
        if oprot._fast_encode is not None and self.thrift_spec is not None:
            oprot.trans.write(oprot._fast_encode(self, (self.__class__, self.thrift_spec)))
            return
        oprot.writeStructBegin('ping_args')
        oprot.writeFieldStop()

     ...


class TBinaryProtocol(TProtocolBase):
    def writeMessageBegin(self, name, type, seqid):
        if self.strictWrite:
            self.writeI32(TBinaryProtocol.VERSION_1 | type)
            self.writeString(name)
            self.writeI32(seqid)
        else:
            self.writeString(name)
            self.writeByte(type)
            self.writeI32(seqid)

    ...

如上述代码(截取了部分方法)可知,client stub通过其输出协议类对象self._oprot封装各部分数据:消息类型(此时是TMessageType.CALL)、所调用服务接口中的方法名(此时调用的是ping()'ping')、_seqid、所需参数(独立了一个ping_args类),协议类对象binary_protocol将这些数据输出传输层对象(即TTransport工厂类生产的)。 
ps: 协议类在封装请求服务接口方法必要的数据时,将其整个作为一个消息体(writeMessageBegin()writeMessageEnd(), name为服务接口的方法名'ping'),所有参数作为消息体中的一个struct(writeStructBegin()writeStructEnd()name为服务接口方法名加上argsping_args),参数每个值是该strut中的field(writeFieldBeginwriteFieldEnd,ps:虽然指定了参数名,但在封装时确实按.thrift文件中服务接口方法参数中定义的id来处理的,写入了该参数字段的id和类型),至此协议类将所需数据封装成消息体,传递给传输层(集合类型list map set也有相对应的begin和end方法)。 
3. 回头再看ping()方法

class Client(Iface):

    def send_ping(self):
        self._oprot.writeMessageBegin('ping', TMessageType.CALL, self._seqid)
        args = ping_args()
        args.write(self._oprot)
        self._oprot.writeMessageEnd()
        self._oprot.trans.flush()

    ...

self._oprot.writeMessageEnd()协议层封装完后调用了传输层的flush方法self._oprot.trans.flush()。简单以传输层TBufferedTransport类为例来看一下该方法。

class TBufferedTransport(TTransportBase, CReadableTransport):
    DEFAULT_BUFFER = 4096

    def __init__(self, trans, rbuf_size=DEFAULT_BUFFER):
        self.__trans = trans
        self.__wbuf = BufferIO()
        # Pass string argument to initialize read buffer as cStringIO.InputType
        self.__rbuf = BufferIO(b'')
        self.__rbuf_size = rbuf_size

    def flush(self):
        out = self.__wbuf.getvalue()
        # reset wbuf before write/flush to preserve state on underlying failure
        self.__wbuf = BufferIO()
        self.__trans.write(out)

    ...

可以发现在初始化了写缓存,读缓存,读缓存大小以及self.__trans。该self.__trans一般是一个TSocket(实际上TSocket也是继承了传输层基类TTransportBase)。self.__trans.write(out)实际上是TSocket.write()

class TSocket(TSocketBase):
    def open(self):
        if self.handle:
            raise TTransportException(TTransportException.ALREADY_OPEN)
        try:
            addrs = self._resolveAddr()
        except socket.gaierror:
            msg = 'failed to resolve sockaddr for ' + str(self._address)
            logger.exception(msg)
            raise TTransportException(TTransportException.NOT_OPEN, msg)
        for family, socktype, _, _, sockaddr in addrs:
            handle = self._do_open(family, socktype)
            handle.settimeout(self._timeout)
            try:
                handle.connect(sockaddr)
                self.handle = handle
                return
            except socket.error:
                handle.close()
                logger.info('Could not connect to %s', sockaddr, exc_info=True)
        msg = 'Could not connect to any of %s' % list(map(lambda a: a[4],
                                                          addrs))
        logger.error(msg)
        raise TTransportException(TTransportException.NOT_OPEN, msg)

    def write(self, buff):
        if not self.handle:
            raise TTransportException(type=TTransportException.NOT_OPEN,
                                      message='Transport not open')
        sent = 0
        have = len(buff)
        while sent < have:
            plus = self.handle.send(buff)
            if plus == 0:
                raise TTransportException(type=TTransportException.END_OF_FILE,
                                          message='TSocket sent 0 bytes')
            sent += plus
            buff = buff[plus:]

    ...

前提需要调用open()方法来建立TCP连接,self.handle是一个套接字对象,然后调用self.handle.send()来向服务端输出数据。在plus = self.handle.send(buff)时实际上客户端已经完成了发送请求的动作,plus即是发送的数据长度,服务端是如何接受数据并调用实际方法产生结果并返回的呢? 
5. 以TSimpleServer单线程阻塞服务器为例简单分析服务端接收到client stub传送过来的数据后所做的操作。

server = TServer.TSimpleServer(pingpong_processor, server_transport, tfactory, tfactory, binary_pfactory, binary_pfactory)
server.serve()

首先需要实例化一个TSimpleServer对象,并调用server.serve()使其开启监听。在实例化该服务器TSimpleServer对象时指定了服务接口处理类、server_transport实际上是一个TServerSocket(只封装了部分服务端相关的方法),与客户端的TSocket对应,都封装了socket的部分操作、输入/输出传输层工厂类、输入/输出协议层工厂类。

class TSimpleServer(TServer):
    """Simple single-threaded server that just pumps around one transport."""

    def __init__(self, *args):
        TServer.__init__(self, *args)

    def serve(self):
        self.serverTransport.listen()
        while True:
            client = self.serverTransport.accept()
            if not client:
                continue
            itrans = self.inputTransportFactory.getTransport(client)
            otrans = self.outputTransportFactory.getTransport(client)
            iprot = self.inputProtocolFactory.getProtocol(itrans)
            oprot = self.outputProtocolFactory.getProtocol(otrans)
            try:
                while True:
                    self.processor.process(iprot, oprot)
            except TTransport.TTransportException:
                pass
            except Exception as x:
                logger.exception(x)

            itrans.close()
            otrans.close()


class TServerSocket(TSocketBase, TServerTransportBase):
    """Socket implementation of TServerTransport base."""

    def __init__(self, host=None, port=9090, unix_socket=None, socket_family=socket.AF_UNSPEC):
        self.host = host
        self.port = port
        self._unix_socket = unix_socket
        self._socket_family = socket_family
        self.handle = None

    def listen(self):
        res0 = self._resolveAddr()
        socket_family = self._socket_family == socket.AF_UNSPEC and socket.AF_INET6 or self._socket_family
        for res in res0:
            if res[0] is socket_family or res is res0[-1]:
                break

        # We need remove the old unix socket if the file exists and
        # nobody is listening on it.
        if self._unix_socket:
            tmp = socket.socket(res[0], res[1])
            try:
                tmp.connect(res[4])
            except socket.error as err:
                eno, message = err.args
                if eno == errno.ECONNREFUSED:
                    os.unlink(res[4])

        self.handle = socket.socket(res[0], res[1])
        self.handle.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        if hasattr(self.handle, 'settimeout'):
            self.handle.settimeout(None)
        self.handle.bind(res[4])
        self.handle.listen(128)

    def accept(self):
        client, addr = self.handle.accept()
        result = TSocket()
        result.setHandle(client)
        return result

通过上述的两段代码可以清楚的了解到在server.serve()后,实际上会创建一个socket来监听其服务地址端口,并开启循环阻塞等待直到self.handle.accept()获取到数据,用获取到的数据构造并返回一个新的TSocket对象,利用该对象和之前传入的工厂类来生成了相应输入/输出传输层,输入/输出协议类(输入的用于读取客户端传来的数据,输出的用于返回客户端结果)。 
6. 执行服务接口方法处理类的process()

class Processor(Iface, TProcessor):
    def __init__(self, handler):
        self._handler = handler
        self._processMap = {}
        self._processMap["ping"] = Processor.process_ping

    def process(self, iprot, oprot):
        (name, type, seqid) = iprot.readMessageBegin()
        if name not in self._processMap:
            iprot.skip(TType.STRUCT)
            iprot.readMessageEnd()
            x = TApplicationException(TApplicationException.UNKNOWN_METHOD, 'Unknown function %s' % (name))
            oprot.writeMessageBegin(name, TMessageType.EXCEPTION, seqid)
            x.write(oprot)
            oprot.writeMessageEnd()
            oprot.trans.flush()
            return
        else:
            self._processMap[name](self, seqid, iprot, oprot)
        return True

执行该方法时会先利用输入协议类的读消息体方法获取服务接口方法名、类型、以及seqid,即(name, type, seqid) = iprot.readMessageBegin()。通过解码消息体得到的nameself._processMap中找到对应的方法(此处应为process_ping())并执行。

    
def process_ping(self, seqid, iprot, oprot):
        args = ping_args()
        args.read(iprot)
        iprot.readMessageEnd()
        result = ping_result()
        try:
            result.success = self._handler.ping()
            msg_type = TMessageType.REPLY
        except (TTransport.TTransportException, KeyboardInterrupt, SystemExit):
            raise
        except Exception as ex:
            msg_type = TMessageType.EXCEPTION
            logging.exception(ex)
            result = TApplicationException(TApplicationException.INTERNAL_ERROR, 'Internal error')
        oprot.writeMessageBegin("ping", msg_type, seqid)
        result.write(oprot)
        oprot.writeMessageEnd()
        oprot.trans.flush()

与之客户端封装方法名、参数等信息对应的,process_ping()中执行了读消息体(readMessageBeginreadMessageEnd)、读消息体中的参数(参数作为一个structreadStructBeginreadStructEnd)、以及读取参数struct中的各个字段(readFieldBeginreadFieldEnd),至此读取出了客户端所传过来的所有数据信息。 
7. 读取完客户端传送过来的信息后找到实际存在与服务端的服务实现类与接口的实现方法,传入相关的参数并获取实际的返回结果。在利用该TSimpleServer的输出传输层和输出协议类封装返回结果(依旧是消息体(返回结果struct(结果字段field))的格式),最终依旧调用的是输出传输层的TSocket.flush(),即self.handle.send(buff)利用套接字socket来传送数据返回给客户端,至此服务端的工作告一段落。 
8. 重新返回客户端来看,当客户端将所有数据发送完毕后,即执行完Client.send_ping()方法后,利用Client.recv_ping()来接受服务端的返回结果,与服务端接受并解析消息体类似(readMessageBegin()等等),最终得到实际的结果(若服务器阻塞了,那执行Client.recv_ping()读取返回消息时会是什么情况?读取消息时,协议类的readMessageBegin()等会调用传输层的read(),而传输层的read()实际是调用了TSocket.read(),即套接字socket.recv(),该方法是会阻塞等待的)。 
9. 处理服务端返回的实际结果,关闭TSocket,结束本次远程调用。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值