RPC及Thrift介绍

  • 2019-03-16
  • 398
  • 1

一、RPC

1. RPC是什么

RPC(Remote Procedure Call )——远程过程调用,它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议。RPC协议假定某些传输协议的存在,如TCP或UDP,为通信程序之间携带信息数据。在OSI网络通信模型中,RPC跨越了传输层应用层。RPC使得开发包括网络分布式多程序在内的应用程序更加容易。

RPC采用客户机/服务器模式。请求程序就是一个客户机,而服务提供程序就是一个服务器。首先,客户机调用进程发送一个有进程参数的调用信息到服务进程,然后等待应答信息。在服务器端,进程保持睡眠状态直到调用信息到达为止。当一个调用信息到达,服务器获得进程参数,计算结果,发送答复信息,然后等待下一个调用信息,最后,客户端调用进程接收答复信息,获得进程结果,然后调用执行继续进行。

2. 为什么要用RPC?

其实这是应用开发到一定的阶段的强烈需求驱动的。

  1.  当我们的系统访问量增大、业务增多时,我们会发现一台单机运行此系统已经无法承受。此时,我们可以将业务拆分成几个互不关联的应用,分别部署在各自机器上,以划清逻辑并减小压力。此时,我们也可以不需要RPC,因为应用之间是互不关联的。
  2. 当我们的业务越来越多、应用也越来越多时,自然的,我们会发现有些功能已经不能简单划分开来或者划分不出来。此时,可以将公共业务逻辑抽离出来,将之组成独立的服务Service应用 。而原有的、新增的应用都可以与那些独立的Service应用 交互,以此来完成完整的业务功能。所以此时,我们急需一种高效的应用程序之间的通讯手段来完成这种需求。
    其实(2)描述的场景也是服务化 、微服务 和分布式系统架构 的基础场景。即RPC框架就是实现以上结构的有力方式。

3. RPC的原理和框架

Nelson 的论文中指出实现 RPC 的程序包括 5 个部分:

1. User

2. User-stub

3. RPCRuntime

4. Server-stub

5. Server

这 5 个部分的关系如下图所示

这里 user 就是 client 端,当 user 想发起一个远程调用时,它实际是通过本地调用user-stub。user-stub 负责将调用的接口、方法和参数通过约定的协议规范进行编码并通过本地的 RPCRuntime 实例传输到远端的实例。远端 RPCRuntime 实例收到请求后交给 server-stub 进行解码后发起本地端调用,调用结果再返回给 user 端。

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

发表评论

冀ICP备19026961号