RPC 基本原理与 Apach Thrift 初体验

概述

RPC(Remote Procedure Call,远程过程调用)是一个计算机通信协议,此协议允许进程间通信。简单来说,当机器 A 上的进程调用机器 B 上的进程时,A 上的调用进程被挂起,而 B 上的被调用进程开始执行。调用方可以通过参数将信息传送给被调用方,然后可以通过被调用方传回的结果得到返回。RPC 框架屏蔽了底层传输方式(TCP/UDP)、序列化和反序列化(XML/JSON/二进制)等内容,使用框架只需要知道被调用者的地址和接口就可以了,无须额外地为这些底层内部编程。
目前主流的 RPC 框架有如下几种。

  • Thrift:Facebook 开源的跨语言框架
  • gRPC:Google 基于 HTTP/2 和 Protobuf 的能用框架
  • Avro:Hadoop 的子项目

本文讲述 RPC 的基本原理并以 Thrift 框架为例说明 RPC 的使用。

基本原理

这里写图片描述
图1:客户端与服务器之间的 RPC 通信过程

图1描述了客户端与服务器 RPC 通信的基本过程,具体来说,包括几下步骤:
(1)客户过程以正常方式调用客户桩(client stub,一段代码);
(2)客户桩生成一个消息,然后调用本地操作系统;
(3)客户端操作系统将消息发送给远程操作系统;
(4)远程操作系统将消息交给服务器桩(server stub,一段代码);
(5)服务器桩将参数提取出来,然后调用服务器过程;
(6)服务器执行要求的操作,操作完成后将结果返回给服务器桩;
(7)服务器桩将结果打包成一个消息,然后调用本地操作系统;
(8)服务器操作系统将含有结果的消息发送回客户端操作系统;
(9)客户端操作系统将消息交给客户桩;
(10)客户桩将结果从从消息中提取出来,返回给调用它的客户过程。
所有这些步骤的效果是,将客户过程对客户桩发出的本地调用转换成对服务器过程的本地调用,而客户端和服务器都不会意识到有中间步骤的存在。
这个时候,你可能会想,既然是调用另一台机器的服务,使用 RESTful API 也可以实现啊,为什么要选择 RPC 呢?我们可以从两个方面对比:

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

对接异构第三方服务时,通常使用 HTPP/RESTful 等公有协议,对于内部的服务调用,应用选择性能更高的二进制私有协议。

Thrift 架构

说完 RPC 的一般性原理,我们再来看目前流行的 RPC 框架—— Apache Thrift。Apache Thrift 最初是 Facebook 实现的一种支持多种编程语言、高效的远程服务器调用框架,它于 2008 年进入 Apache 开源项目。Apache Thrift 采用接口描述语言(IDL)定义 RPC 接口和数据类型,通过编译器生成不同语言的代码(支持 C++,Java,Python,Ruby等),其数据传输采用二进制格式,相对 XML 和 JSON 来说体积更小,对于高并发、大数据量和多语言的环境更有优势。在 Facebook,Apache Thrift 正是使用于其内部服务的通信,其稳定性和高性能已在生产环境中得到证明的。
Apache Thrift 的架构如图2所示。

这里写图片描述
图2:Apache Thrift 客户/服务器架构

由图2可以看到,Apache Thrift 仍然是基于图1所示的 RPC 通信的原理。图2黄色部分是用户实现的业务逻辑,接下来两层是 Apache Thrift 根据 IDL 生成的客户端和服务端代码(其中红色部分用来实现数据的读写操作),对应于图1中的 client stub 和 server stub。TProtocol 用来对数据进行序列化与反序列化,具体方法包括二进制,JSON 或者 Apache Thrift 定义的格式。TTransport 提供数据传输功能,使用 Apache Thrift 可以方便地定义一个服务并选择不同的传输协议。

Thrift 使用

废话少说,我们来看下 Thrift 是如何使用的。

安装 Thrift

我们以 Mac OS 为例,说明如何安装 Thrift ,安装过程具体可以参考 http://thrift.apache.org/docs/install/os_x
(1)安装 boost
(2)安装 libevent
(3)安装 thrift
安装过程还算顺利,如果出现 openssl 头文件找不到的情况,可以通过给 make 命令添加参数的方式解决,例如:

1
make CPPFLAGS="-I/usr/local/Cellar/openssl/1.0.2h_1/include" LDFLAGS="-L/usr/local/Cellar/openssl/1.0.2h_1/lib"

安装完成后,使用命令来测试一下安装是否成功:

1
$ thrift -version

可以看到输出:

Thrift version 0.10.0

说明已正确安装。

定义 IDL 文件

安装好 Thrift 后,我们便可以编写 IDL 文件。Thrift 使用 .thrift 文件来定义接口和数据类型。
下面是一个简单的 .thrift 文件示例:

1
2
3
4
5
typedef i32 int
service MultiplicationService
{
int multiply(1:int n1, 2:int n2),
}

为了简单起见,我们只定义了一个接口。解析一下 .thrift 文件的语法:

  • typedef 用来定义 Thrift 类型名称的别名。Thrift 中拥有自己的类型系统,其中 i32 为32位有符号整形。
  • service 用来定义 Thrift 的服务接品,在上面我们定义一个 MultiplicationService,其中包含了一个接口:multiply。接口的第一个字段表示接口返回的类型比如上面返回一个 i32 类型。接口的参数需要定义其类型和顺序。

编写好 .thrift 文件后,将上面的文件保存为 multiplication.thrift,然后,便可以使用 thrift 命令来对其进行编译。

1
thrift --gen py multiplication.thrift

执行此命令后,会在当前目录生成一个 gen-py 的目录,进入 gen-py 目录,可以看到其中包含以下的文件:

这里写图片描述
图3:编译 .thrift 文件后生成的 Python 代码

生成的 MultiplicationService.py 包含了 RPC 通信所需的客户端和服务端代码,以及数据 read/write 操作代码。

实现服务端

我们在 gen-py 相同的目录下编写服务端与客户端的实现代码。首先是服务端代码 multiserver.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import sys
sys.path.append('gen-py')
from multiplication import MultiplicationService
from multiplication.ttypes import *

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

class MultiplicationHandler(object):
# multiplication.thrift 文件中已对 `multiploy` 方法进行了定义,参数为两个整型
# 返回值为一整型
def multiply(self, n1, n2):
print('multify n1 * n2, n1: %s, n2: %s, result: %s' %
(n1, n2, n1 * n2))
return n1 * n2

if __name__ == '__main__':
handler = MultiplicationHandler()
# Processor 用来从连接中读取数据,将处理授权给 handler(自己实现),
# 最后将结果写到连接上
processor = MultiplicationService.Processor(handler)
# 服务端使用 9090 端口, transport 是网络读写抽象层,为到来的连接创建传输对象
transport = TSocket.TServerSocket(port=9090)
tfactory = TTransport.TBufferedTransportFactory()
pfactory = TBinaryProtocol.TBinaryProtocolFactory()
server = TServer.TSimpleServer(processor, transport, tfactory, pfactory)
print('Starting the server...')
server.serve()
print('done.')

multiserver.py 首先对 multiplication.thrift 文件中的 multiply 方法进行了实现,这样当服务端接收到客户端请求后会调用此方法进行处理。
Processor 类则主要完成两个事情:从连接中读取数据,往连接中写入数据。当然中间会将数据交给 handler 处理。
服务端启动后,会监听 9090 端口,接受客户端的连接。

实现客户端

我们在 multiclient.py 实现客户端的代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import sys
sys.path.append('gen-py')
from multiplication import MultiplicationService
from multiplication.ttypes import *

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

try:
# 同样使用 9090 端口,使用阻塞式 I/O 进行传输,是最常见的模式
transport = TSocket.TSocket('localhost', 9090)
transport = TTransport.TBufferedTransport(transport)
# 封装协议,使用二进制编码格式进行传输
protocol = TBinaryProtocol.TBinaryProtocol(transport)
# 创建一个 client
client = MultiplicationService.Client(protocol)
# 打开连接
transport.open()

n1 = 5
n2 = 7
product = client.multiply(n1, n2)
print '%s * %s = %s' % (n1, n2, product)

# 关闭连接
transport.close()
except Thrift.TException, tx:
print '%s' % (tx.message)

在上面的代码中,我们创建了一个 Client 对象,Client 类的代码由 .thrift 生成。然后我们调用 multiply 发送 RPC 请求,并取得返回值。

测试

打开一个终端,执行命令,运行服务端程序:

1
python multiserver.py

打开另一个终端,执行命令,运行客户端程序:

1
python multiclient.py

此时,服务端终端输出:

multify n1 * n2, n1: 5, n2: 7, result: 35

客户端终端输出:

5 * 7 = 35

说明客户端已正常发送 RPC 请求,并由服务端处理,客户端成功接收返回。

总结

本文简述了 RPC 的基本原理与 Apache Thrift 的使用步骤。通过此文章,希望大家对于 RPC 和 Apache Thrift 有个初步的了解。对于 Apache Thrift 更深入的分析,可以参考本人的系列文章。

参考资料

  1. http://thrift.apache.org
  2. Python Web开发实践,董伟明著,中国工信出版集团,电子工业出版社
  3. https://www.cs.rutgers.edu/~pxk/417/notes/03-rpc.html
  4. https://www.zhihu.com/question/25536695
  5. https://en.wikipedia.org/wiki/Apache_Thrift
  6. http://thrift.apache.org/docs/install/os_x
  7. http://thrift-tutorial.readthedocs.io/en/latest/usage-example.html
  8. http://www.thrift.pl
  9. http://techlabs.emag.ro/introduction-to-apache-thrift/
  10. https://thrift.apache.org/static/files/thrift-20070401.pdf
  11. http://jnb.ociweb.com/jnb/jnbJun2009.html