thrift 初探

读研以来,很少关注高层架构的变动,所以忽略了期间服务化趋势带来的一些变化。近期,在某厂的面试中就被问到了服务调度和容错。

目前,各厂基本都已进入了服务化的阶段,对于单节点的性能挖掘变得不再是那么迫切,服务调度、容错等问题才是焦点。之前通过开发服务器,基本掌握了Linux用户态的程序开发,但对于服务化方面的知识和经验,还是相当欠缺。

预期和现实之间的差距,才是症结所在。受限于本人目前所拥有的硬件资源,只能从最基本的RPC开始。

0x00 前言


thrift是目前用得比较多的一款RPC框架,第一次听说thrift应该是大二时,只不过那时并没有需求,也就没有深入。

个人认为,一款RPC(Remote Procedure Call)框架至少应具备如下特征:

  1. 低层通信实现对调用者透明
  2. 支持不同的序列化表示,例如Json或二进制协议
  3. 支持不同的程序语言

显然thrift完全做到了上述几点,除此之外,它也做到了:

  1. 可替换的通信方式
  2. 可替换的序列化协议
  3. 多种服务模型,单线程、多线程、线程池等

得益于分层架构,对thrift进行扩展,难度并不大,虽然如此,我个人认为在内网,应该还是二进制协议+TCP为主,如果需要穿透防火墙,或许使用JSON+HTTP会更加合适。

0x01 编译安装


ubuntu 16.04的repository中有thrift-compiler这个包,但似乎并没有libthrift,所以为了使用最新的thrift,索性编译一个吧。

我在实验室台式机上的环境是ubuntu 16.04 server版,基本可以算是minimal安装,所以遇到一些依赖问题是在所难免的。有些问题,desktop版或许就不会遇到,因此各位请自行判断,话说thrift官方文档还真是简陋。

一些依赖:

  1. pkg-config:若不安装,会在configure时出现一个奇怪的错误,关键字QT-CORE
  2. libevent:不安装就无法开启非阻塞Server的支持
  3. boost:thrift几乎是重度依赖boost库
  4. libssl-dev:Transport层中有用到openssl
  5. flex和bison:用于生成IDL的解析前端
  6. 其它:libtool、automake、autoconf等,看错误安装即可

首先,生成configure:

./bootstrap

默认情况下,thrift会编译所有语言的库,如果你不需要该语言,在configure时需要加入:

--with-LANG=no 或 --without-LANG

你也可以通过如下命令查看选项:

./configure --help

接下来,make和make install。看得出来,官方文档基本是懒得写这些了,但作为初学者,这些还是挺重要的。要是连编译都搞不定,还怎么学整个框架?

0x02 thrift的架构


thrift采用的是严格的分层架构:

+-------------------------------------------+
| Server                                    |
| (single-threaded, event-driven etc)       |
+-------------------------------------------+
| Processor                                 |
| (compiler generated)                      |
+-------------------------------------------+
| Protocol                                  |
| (JSON, compact etc)                       |
+-------------------------------------------+
| Transport                                 |
| (raw TCP, HTTP etc)                       |
+-------------------------------------------+

1. Transport层

用于实现具体的IO操作,但就通讯方式而言,你就可以选择如下类:

  • Socket:TSocket
  • Pipe:TPipe
  • File:TFileTransport
  • SSL:TSSLSocket
  • Http:THttpTransport

还有一些没有提到的类,比如TBufferTransports,它就为基础的Transport提供Buffer,例如TSocket。

关于TTransport,是需要手动open和close的,别忘了。

2. Protocol层

涉及具体的序列化协议,在构造TProtocol的具体类时,需要传入TTransport类的实例。和Transport层一样,支持多种类型:

  • Binary:TBinaryProtocol
  • JSON:TJSONProtocol
  • Compact:TCompactProtocol

这一层的继承体系比Transport层清晰很多,不会出现一层套一层的情况。

3. Processor层

由compiler生成的,使用者需要自行实现具体的逻辑,除了具体的Handler之外,还有对应的工厂类,可以有选择地实现。

4. Server层

支持不同的服务模型:

  • 单线程:TSimpleServer
  • 多线程:TThreadedServer
  • 线程池:TThreadPoolServer
  • 非阻塞:TNonblockingServer

构造Server的时候,你需要传入如下内容:

  1. 生成的Processor,或Processor工厂类
  2. 具体的TTransport,例如TServerSocket
  3. TTransport的工厂类
  4. TProtocol的工厂类

0x03 Client和Server的实现


就拿tutorial中的例子来说,需要做3件事:

  1. compiler根据service生成了具体的接口,位于gen-cpp/Calculator,需要在client中实现CalculateIf
  2. 如果需要,也可实现CalculatorIfFactory
  3. 实现client
  4. 实现server

1. 实现Client

boost::shared_ptr<TTransport> socket(new TSocket("localhost", 9090));
boost::shared_ptr<TTransport> transport(new TBufferedTransport(socket));
boost::shared_ptr<TProtocol> protocol(new TBinaryProtocol(transport));
CalculatorClient client(protocol);

try {
    transport->open();
    
    client.ping();
    // ...    

    transport->close();
} catch(TException& ex) {
    // ...
}

很简洁,但是不要忘了open和close,其次异常的catch也不能忽视,尽管不太习惯在C++中使用异常。

2. 实现Server

这里以多线程模型为例:

TThreadedServer server(
    boost::make_shared<CalculatorProcessorFactory>(boost::make_shared<CalculatorCloneFactory>()),
    boost::make_shared<TServerSocket>(9090),
    boost::make_shared<TBufferedTransportFactory>(),
    boost::make_shared<TBinaryProtocolFactory>()
);

server.serve();

从示例中看,还是挺简洁的,但是这里有不同的服务模型,所以需要完全掌握,还是要稍微花一点时间的。

0xFF 总结


经过端午假期的学习,可以说大概明白了thrift是怎么实现的,给他人所留的造轮子的空间确实不大。

当然也不是说没有小缺陷:

  1. 目前thrift对于boost库的依赖还是较重的,很多特性在C++11中已经实现
  2. 官方的文档太简陋,无论是细节还是具体的API,完全就是程序员随手撸的

官方提供的whitepaper,阅读体验倒是好得多,毕竟是两栏的,只不过内容似乎有点旧。

上述毕竟只是“初探”,很多内容没有涉及,诸如IDL,内部实现等,等之后大概读完thrift的lib后另起一篇。

说两句: