RPC及Thrift介绍
一、RPC
1. RPC是什么
RPC(Remote Procedure Call )——远程过程调用,它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议。RPC协议假定某些传输协议的存在,如TCP或UDP,为通信程序之间携带信息数据。在OSI网络通信模型中,RPC跨越了传输层和应用层。RPC使得开发包括网络分布式多程序在内的应用程序更加容易。
RPC采用客户机/服务器模式。请求程序就是一个客户机,而服务提供程序就是一个服务器。首先,客户机调用进程发送一个有进程参数的调用信息到服务进程,然后等待应答信息。在服务器端,进程保持睡眠状态直到调用信息到达为止。当一个调用信息到达,服务器获得进程参数,计算结果,发送答复信息,然后等待下一个调用信息,最后,客户端调用进程接收答复信息,获得进程结果,然后调用执行继续进行。
2. 为什么要用RPC?
其实这是应用开发到一定的阶段的强烈需求驱动的。
- 当我们的系统访问量增大、业务增多时,我们会发现一台单机运行此系统已经无法承受。此时,我们可以将业务拆分成几个互不关联的应用,分别部署在各自机器上,以划清逻辑并减小压力。此时,我们也可以不需要RPC,因为应用之间是互不关联的。
- 当我们的业务越来越多、应用也越来越多时,自然的,我们会发现有些功能已经不能简单划分开来或者划分不出来。此时,可以将公共业务逻辑抽离出来,将之组成独立的服务Service应用 。而原有的、新增的应用都可以与那些独立的Service应用 交互,以此来完成完整的业务功能。所以此时,我们急需一种高效的应用程序之间的通讯手段来完成这种需求。
其实(2)描述的场景也是服务化 、微服务 和分布式系统架构 的基础场景。即RPC框架就是实现以上结构的有力方式。
3. RPC的原理和框架
Nelson 的论文中指出实现 RPC 的程序包括 5 个部分:
1. User
2. User-stub
3. RPCRuntime
4. Server-stub
5. Server
这 5 个部分的关系如下图所示
RPC的对象在网络中是以二进制数据的方式传输的,所以这是一个序列化与反序列化的过程。
二、Thrift框架
1. Thrift是什么
Thrift是一个跨语言的服务部署框架,最初由Facebook于2007年开发,2008年进入Apache开源项目。Thrift是RPC实现方法的一种,Thrift通过IDL(Interface Definition Language,接口定义语言)来定义RPC的接口和数据类型,然后通过thrift编译器生成不同语言的代码(目前支持C++,Java, Python, PHP, Ruby, Erlang, Perl, Haskell, C#, Cocoa, Smalltalk和OCaml),并由生成的代码负责RPC协议层和传输层的实现。
2. Thrift架构
图中,TProtocol(协议层),定义数据传输格式,例如:
- TBinaryProtocol:二进制格式;
- TCompactProtocol:压缩格式;
- TJSONProtocol:JSON格式;
- TSimpleJSONProtocol:提供JSON只写协议, 生成的文件很容易通过脚本语言解析;
- TDebugProtocol:使用易懂的可读的文本格式,以便于debug
TTransport(传输层),定义数据传输方式,可以为TCP/IP传输,内存共享或者文件共享等)被用作运行时库。
- TSocket:阻塞式socket;
- TFramedTransport:以frame为单位进行传输,非阻塞式服务中使用;
- TFileTransport:以文件形式进行传输;
- TMemoryTransport:将内存用于I/O,java实现时内部实际使用了简单的ByteArrayOutputStream;
- TZlibTransport:使用zlib进行压缩, 与其他传输方式联合使用,当前无java实现;
Thrift支持的服务模型
- TSimpleServer:简单的单线程服务模型,常用于测试;
- TThreadPoolServer:多线程服务模型,使用标准的阻塞式IO;
- TNonblockingServer:多线程服务模型,使用非阻塞式IO(需使用TFramedTransport数据传输方式);
Thrift实际上是实现了C/S模式,通过代码生成工具将thrift文件生成服务器端和客户端代码(可以为不同语言),从而实现服务端和客户端跨语言的支持。用户在Thirft文件中声明自己的服务,这些服务经过编译后会生成相应语言的代码文件,然后客户端调用服务,服务器端提供服务。
3. Thrift的安装和使用
安装参见官方文档
首先我们需要先编写thrift文件,其中包含变量声明(variable)、数据声明(struct)和服务接口声明(service, 可以继承其他接口)等。
以下是一个较为完整的thrift文件,用来说明各个部分的写法和含义。
// 包含头文件
include "shared.thrift"
// 指定目标语言
namespace cpp tutorial
// 定义变量
const i32 INT32CONSTANT = 9853
// 定义结构体
struct Work {
1: i32 num1 = 0,
2: i32 num2,
3: Operation op,
4: optional string comment,
}
// 定义服务
service Calculator extends shared.SharedService {
/**
* A method definition looks like C code. It has a return type, arguments,
* and optionally a list of exceptions that it may throw. Note that argument
* lists and exception lists are specified using the exact same syntax as
* field lists in struct or exception definitions.
*/
void ping(),
i32 add(1:i32 num1, 2:i32 num2),
i32 calculate(1:i32 logid, 2:Work w) throws (1:InvalidOperation ouch),
/**
* This method has a oneway modifier. That means the client only makes
* a request and does not listen for any response at all. Oneway methods
* must be void.
*/
oneway void zip()
}
下面我们用一个例子来实战操作一下。
首先编写一个demo.thrift文件
service ARCloudDemo {
void ping()
i32 set_value(1:string k, 2:string v)
}
然后执行thrift --gen cpp demo.thrift
生成cpp版本的代码,存放在gen-cpp目录下;
接下来执行thrift --gen java demo.thrift
生成python版本的代码,存放在gen-py目录下。
进入gen-cpp目录,其中ARCloudDemo_server.skeleton.cpp是自动生成的server端代码,我们只需要直接填充一下函数的实现就可以了。
......(略)
void ping() {
// Your implementation goes here
printf("ping\n");
}
int32_t set_value(const std::string& k, const std::string& v) {
// Your implementation goes here
printf("set_value, k = %s, v = %s\n", k.c_str(), v.c_str());
}
......(略)
p.s. 在这里,我为了项目代码可读性更强,将ARCloudDemo_server.skeleton.cpp重命名为了server.cpp
执行以下命令
g++ ARCloudDemo.cpp server.cpp demo_constants.cpp demo_types.cpp -o server -std=c++11 -lthrift
编译生成server端程序。
而client端代码则需要我们自己去写,进入gen-py目录,创建client.py
import time
import sys
import glob
sys.path.append('gen-py')
#sys.path.insert(0, glob.glob('../../lib/py/build/lib*')[0])
# 多进程请求
from multiprocessing import Pool
# ARCloudDemo服务
from demo import ARCloudDemo
from thrift import Thrift
from thrift.transport import TSocket
from thrift.transport import TTransport
from thrift.protocol import TBinaryProtocol
def run(a, b):
# Make socket
transport = TSocket.TSocket('localhost', 9090)
# 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 = ARCloudDemo.Client(protocol)
# Connect!
transport.open()
# 在这里我们就调用了set_value接口,而且是远程调用,thrift框架把底层传输和数据序列化都封装好了。
ret = client.set_value(a, b)
transport.close()
return ret
if __name__ == "__main__":
if len(sys.argv) != 3:
print("client.py val_a val_b")
sys.exit(0)
a = sys.argv[1]
b = sys.argv[2]
start_time = time.time()
ret = run(a, b)
end_time = time.time()
print("server return:", ret)
print("time=", end_time-start_time)
先运行之前编译好的server端程序./gen-cpp/server
,再运行client端程序python3 client.py val_a val_b
。
我们可以看到,server端程序输出了set_value, k = val_a, v = val_b
,也就说明thrift通信已建立成功。
zjw1111
部分内容转载自:https://blog.csdn.net/kingcat666/article/details/78577079