因为一些历史原因需要在不同服务器之间建立rpc通道,来满足不定时、不定量且数据量可能会大的函数需要。在简单对比了之后投向了thrift,虽然网上大佬们说thrift的文档做的很烂,但具体有多烂这个我也不知道,可能基础需求来讲也不重要。

Q: 为什么选择rpc而不是RESTful API的方式?
A:
资源粒度。RPC 就像本地方法调用,RESTful API 每一次添加接口都可能需要额外地组织开放接口的数据,这相当于在应用视图中再写了一次方法调用,而且它还需要维护开发接口的资源粒度、权限等;
流量消耗。RESTful API 在应用层使用 HTTP 协议,哪怕使用轻型、高效、传输效率高的 JSON 也会消耗较大的流量,而 RPC 传输既可以使用 TCP 也可以使用 UDP,而且协议一般使用二制度编码,大大降低了数据的大小,减少流量消耗。

Thrift的网络栈结构:
image.png

下面两层的数据传输:

TTransport层

代表thrift的数据传输方式,thrift定义了如下几种常用数据传输方式

  • TSocket: 阻塞式socket;
  • TFramedTransport: 以frame为单位进行传输,非阻塞式服务中使用;
  • TFileTransport: 以文件形式进行传输;

TProtocol层

代表thrift客户端和服务端之间传输数据的协议,通俗来讲就是客户端和服务端之间传输数据的格式(例如json等),thrift定义了如下几种常见的格式

  • TBinaryProtocol: 二进制格式;
  • TCompactProtocol: 压缩格式;
  • TJSONProtocol: JSON格式;
  • TSimpleJSONProtocol: 提供只写的JSON协议;

thrift支持的Server模型

thrift主要支持以下几种服务模型

  • TSimpleServer: 简单的单线程服务模型,常用于测试;
  • TThreadPoolServer: 多线程服务模型,使用标准的阻塞式IO;
  • TNonBlockingServer: 多线程服务模型,使用非阻塞式IO(需要使用TFramedTransport数据传输方式);
  • THsHaServer: THsHa引入了线程池去处理,其模型读写任务放到线程池去处理,Half-sync/Half-async处理模式,Half-async是在处理IO事件上(accept/read/write io),Half-sync用于handler对rpc的同步处理;

TThreadPoolServerTNonBlockingServer都可以在实际生产中拿来用。在客户端不多的情况下,可以选用TThreadPoolServer,但是要注意TThreadPoolServer的客户端只要不从服务器上断开连接,就会一直占据服务器的一个线程,当服务器线程池所有线程都在被使用时,新到来的客户端将排在队列里等待,直到有客户端断开连接,使服务器端线程池出现空闲线程方可继续被提供服务,所以使用这种模型时,一定要注意客户端不使用时不要长时间连接服务器,如果确实有这种需求,请使用TNonblockingServer。

thrift 接口文件

thrift IDL接口文件不支持无符号的数据类型,因为很多编程语言中不存在无符号类型,thrift支持以下几种基本的数据类型

  • byte: 有符号字节
  • i16: 16位有符号整数
  • i32: 32位有符号整数
  • i64: 63位有符号整数
  • double: 64位浮点数
  • string: 字符串

此外thrift还支持以下容器类型:

  • list: 一系列由T类型的数据组成的有序列表,元素可以重复;
  • set: 一系列由T类型的数据组成的无序集合,元素不可重复;
  • map: 一个字典结构,Key为K类型,Value为V类型,相当于java中的HashMap;

具体写的时候可以参照github上thrift给出的解释和示例:
https://github.com/apache/thr...

Window安装thrift

  1. thrift官网上下载windows exe
  2. C盘新建C:/Thrift文件夹。将thrift的exe改名为thrift.exe
  3. 我的电脑-属性-环境变量,加入C:/Thrift到PATH
  4. cmd中输入thrift -version确认安装成功

Linux安装thrift

环境(阿里云服务器):
Linux version 3.10.0-957.5.1.el7.x86_64 (mockbuild@kbuilder.bsys.centos.org) (gcc version 4.8.5 20150623 (Red Hat 4.8.5-36) (GCC) )

  1. 因为thrift是用c++编译的,需要安装相应的包(需要root权限)

    [root@localhost ~]#yum install automake libtool flex bison pkgconfig gcc-c++ boost-devel libevent-devel zlib-devel python-devel ruby-devel openssl-devel
  2. 从apache网站上下载thrift包(这里以0.14.0版本举例,路径什么的可以按每个人的意愿更改)

    [root@localhost ~]# cd /root/download/
    [root@localhost download]#  wget http://mirror.bit.edu.cn/apache/thrift/0.14.0/thrift-0.14.0.tar.gz
    [root@localhost download]# tar -zxvf thrift-0.14.0.tar.gz 
    
    [root@localhost download]# ls
    thrift-0.14.0  thrift-0.14.0.tar.gz
  3. 配置、编译、安装thrift

    [root@localhost download]# cd thrift-0.14.0
    [root@localhost thrift-0.14.0]# ./configure
    [root@localhost thrift-0.14.0]# make
    [root@localhost thrift-0.14.0]# make install  

python使用

哭了,在网上找例子也太乱了。

1 用TSimpleServer建立测试

  1. pip install thrift
  2. 编写接口文件
    新建一个仓库,比如说叫thrift-gogogo,在文件夹下新建一个空文件叫helloworld.thrift.在里面以文本的形式写

    const string HELLO_YK = "manjie-test"
    service HelloWorld {
        void ping(),
        string sayHello(),
        string sayMsg(1:string msg)
    }

    第一行是可以在thrift里面定义变量,常量的话最后thrift会编到constants.py中。service则是定义了服务的函数

  3. 通过thrift生成相应文件
    通过terminal在对应仓库下用thrift -r --gen py <thrift文件的path>生成一个叫gen-py的文件夹
  4. 按一定结构重新整理仓库
    除了helloworld这个文件是自动生成并从gen-py中移出来之外,client和server文件夹都是手动新建的。
    image.png
  5. 编辑server.py文件

    import sys
    from helloworld import HelloWorld
    from helloworld.ttypes import *
    from thrift.transport import TSocket
    from thrift.transport import TTransport
    from thrift.protocol import TBinaryProtocol
    from thrift.server import TServer
    import socket
    class HelloWorldHandler:
        def __init__(self):
            self.log = {}
        def ping(self):
            print("server function: ping()")
        def sayHello(self):
            print("sayHello()")
            return "say hello from " + socket.gethostbyname(socket.gethostname())
        def sayMsg(self, msg):
            print("sayMsg(" + msg + ")")
            return "say " + msg + " from " + socket.gethostbyname(socket.gethostname())
    sys.path.append('./gen-py')
    handler = HelloWorldHandler()
    processor = HelloWorld.Processor(handler)
    transport = TSocket.TServerSocket('127.0.0.1', 30303)
    tfactory = TTransport.TBufferedTransportFactory()
    pfactory = TBinaryProtocol.TBinaryProtocolFactory()
    server = TServer.TSimpleServer(processor, transport, tfactory, pfactory)
    print("Starting python server...")
    server.serve()
    print("server side: done!")
  6. 编辑client.py

    import sys
    sys.path.append('./gen-py')
    from helloworld import HelloWorld
    from helloworld.ttypes import *
    from helloworld.constants import *
    from thrift import Thrift
    from thrift.transport import TSocket
    from thrift.transport import TTransport
    from thrift.protocol import TBinaryProtocol
    try:
        # Make socket
     transport = TSocket.TSocket('127.0.0.1', 30303)
        # Buffering is critical. Raw sockets are very slow
     transport = TTransport.TBufferedTransport(transport)
        # Wrap in a protocol
     protocol = TBinaryProtocol.TBinaryProtocol(transport)
        # Create a client to use the protocol encoder
     client = HelloWorld.Client(protocol)
        # Connect!
     transport.open()
        client.ping()
        print("client: ping()")
        msg = client.sayHello()
        print(msg)
        msg = client.sayMsg(HELLO_YK)
        print(msg)
        transport.close()
    except Thrift.TException as tx:
        print("%s" % tx.message)
  7. 先运行server.py, 可以看到如下结果:
    image.png
  8. 再运行client.py,可以看到如下结果:
    image.png
    此时再返回看server的运行窗口,会变成
    image.png

2 用TNonblockingServer实现需求

前1-4步和上面的一致,主要区别在于server和client的代码部分

  1. server部分(只标注和SimpleServer设置不同的地方)

    from thrift.server import TNonblockingServer
    
    handler = HelloWorldHandler()
    processor = HelloWorld.Processor(handler)
    transport = TSocket.TServerSocket('127.0.0.1', 30303)
    server = TNonblockingServer.TNonblockingServer(processor, transport)
  2. client部分(只标注和SimpleServer设置不同的地方)

    transport = TTransport.TFramedTransport(transport)

    由于使用了non blocking server 模式,对应的模式必须都改为TFramedTransport

避坑部分

  1. 接口IDL文件和server里一定要有一个ping函数!如果没有定义ping函数的话client.py运行后会报unknown function错误

参考链接:

thrift基本原理
thrift的windows安装
参考的thrift example 【1】
参考的thrift example 【2】
cpp的TNonblockingServer的简单用法


solelysoar
1 声望0 粉丝