You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
constgrpc=require('grpc');constprotoLoader=require('@grpc/proto-loader');constpackageDefinition=protoLoader.loadSync(PROTO_PATH,{keepCase: true,longs: String,enums: String,defaults: true,oneofs: true});consthello_proto=grpc.loadPackageDefinition(packageDefinition).helloworld;/** * Implements the SayHello RPC method. */functionsayHello(call,callback){callback(null,{message: 'Hello '+call.request.name});}/** * Starts an RPC server that receives requests for the Greeter service at the * sample server port */functionmain(){varserver=newgrpc.Server();server.addService(hello_proto.Greeter.service,{sayHello: sayHello});server.bind('0.0.0.0:50051',grpc.ServerCredentials.createInsecure());server.start();}main();
RPC,远程过程调用(Remote Procedure Call,RPC)是一个计算机通信协议,该协议允许运行于一台计算机的程序调用另一台计算机的上的程序。通俗讲,RPC 通过把网络通讯抽象为远程的过程调用,调用远程的过程就像调用本地的子程序一样方便,从而屏蔽了通讯复杂性,使开发人员可以无需关注网络编程的细节,将更多的时间和精力放在业务逻辑本身的实现上,提高工作效率。
RPC 本质上是一种 Inter-process communication(IPC)—— 进程间通信的形式。常见的进程间通信方式如管道、共享内存是同一台物理机上的两个进程间的通信,而 RPC 就是两个在不同物理机上的进程之间的通信。概括的说,RPC 就是在一台机器上调用另一台机器上的方法,这种调用在远程机器上对代码的执行就像在本机上对代码的执行一样,只是迁移了一个执行环境而已。
RPC 是一种 C/S 架构的服务模型,server 端提供接口供 client端 调用,client 端向 server 端发送数据,server 端接收 client 端的数据进行相关计算并将结果返回给 client 端。
为了实现上述 RPC 步骤,许多 RPC 工具被研发出来。这些 RPC 工具大多使用“接口描述语言” —— interface description language (IDL) 来提供跨平台跨语言的服务调用。现在生产中用的最多的 IDL 是Google开源的 protobuf。
常见的开源 RPC 框架
语言平台绑定的开源 RPC 框架
跨语言平台的开源 RPC 框架
接下来开始介绍下 gRPC 与 Thrift RPC 框架。
gRPC
gRPC (gRPC Remote Procedure Calls) 是 Google 发起的一个 开源远程过程调用 (Remote procedure call) 系统。该系统基于 HTTP/2 协议传输,使用 Protocol Buffers 作为 接口描述语言(Interface description language,缩写IDL)。
其他功能包含:
最常见的应用场景是:
接下来通过 gRPC 的架构和 RPC 生命周期的概览来了解 gRPC 的主要概念。
gRPC 服务定义
正如其他 RPC 系统,gRPC 基于如下思想:定义一个服务, 指定其可以被远程调用的方法及其参数和返回类型。gRPC 默认使用 protocol buffers 作为接口定义语言,来描述服务接口和有效载荷消息结构。如果有需要的话,可以使用其他替代方案。
gRPC 定义了四种类型的服务接口方法:
使用 API 接口
gRPC 提供 protocol buffer 编译插件,能够从一个服务定义的 .proto 文件生成客户端和服务端代码。通常 gRPC 用户可以在服务端实现这些API,并从客户端调用它们。
同步 vs 异步
同步 RPC 调用一直会阻塞直到从服务端获得一个应答,这与 RPC 希望的抽象最为接近。另一方面网络内部是异步的,并且在许多场景下能够在不阻塞当前线程的情况下启动 RPC 是非常有用的。
在多数语言里,gRPC 编程接口同时支持同步和异步的特点。
RPC 生命周期
我们来了解当 gRPC 客户端调用 gRPC 服务端的方法时到底发生了什么,大体流程如下:
单项 RPC
首先我们来了解一下最简单的 RPC 形式:客户端发出单个请求,获得单个响应。
服务端流式 RPC
服务端流式 RPC 除了在得到客户端请求信息后发送回一个应答流之外,与我们的简单例子一样。在发送完所有应答后,服务端的状态详情(状态码和可选的状态信息)和可选的跟踪元数据被发送回客户端,以此来完成服务端的工作。客户端在接收到所有服务端的应答后也完成了工作。
客户端流式 RPC
客户端流式 RPC 也基本与我们的简单例子一样,区别在于客户端通过发送一个请求流给服务端,取代了原先发送的单个请求。服务端通常(但并不必须)会在接收到客户端所有的请求后发送回一个应答,其中附带有它的状态详情和可选的跟踪数据。
双向流式 RPC
双向流式 RPC ,调用由客户端调用方法来初始化,而服务端则接收到客户端的元数据,方法名和截止时间。服务端可以选择发送回它的初始元数据或等待客户端发送请求。 下一步怎样发展取决于应用,因为客户端和服务端能在任意顺序上读写 - 这些流的操作是完全独立的。例如服务端可以一直等直到它接收到所有客户端的消息才写应答,或者服务端和客户端可以像"乒乓球"一样:服务端后得到一个请求就回送一个应答,接着客户端根据应答来发送另一个请求,以此类推。
截止时间
gRPC 允许客户端在调用一个远程方法前指定一个最后期限值。这个值指定了在客户端可以等待服务端多长时间来应答,超过这个时间值 RPC 将结束并返回
DEADLINE_EXCEEDED
错误。在服务端可以查询这个期限值来看是否一个特定的方法已经过期,或者还剩多长时间来完成这个方法。 各语言来指定一个截止时间的方式是不同的 - 比如在 Python 里一个截止时间值总是必须的,但并不是所有语言都有一个默认的截止时间。RPC 终止
在 gRPC 里,客户端和服务端对调用成功的判断是独立的、本地的,他们的结论可能不一致。这意味着,比如你有一个 RPC 在服务端成功结束("我已经返回了所有应答!"),到那时在客户端可能是失败的("应答在最后期限后才来到!")。也可能在客户端把所有请求发送完前,服务端却判断调用已经完成了。
取消 RPC
无论客户端还是服务端均可以再任何时间取消一个 RPC 。一个取消会立即终止 RPC 这样可以避免更多操作被执行。它不是一个"撤销", 在取消前已经完成的不会被回滚。当然,通过同步调用的 RPC 不能被取消,因为直到 RPC 结束前,程序控制权还没有交还给应用。
元数据集
元数据是一个特殊 RPC 调用对应的信息(授权详情]) ,这些信息以键值对的形式存在,一般键的类型是字符串,值的类型一般也是字符串(当然也可以是二进制数据)。元数据对 gRPC 本事来说是不透明的 - 它让客户端提供调用相关的信息给服务端,反之亦然。 对于元数据的访问是语言相关的。
流控制
TBD
配置
TBD
频道
在创建客户端存根时,一个 gRPC 频道提供一个特定主机和端口服务端的连接。客户端可以通过指定频道参数来修改 gRPC 的默认行为,比如打开关闭消息压缩。一个频道具有状态,包含
已连接
和空闲
。 gRPC 如何处理关闭频道是语言相关的。有些语言可允许询问频道状态。Node.js 使用 gRPC
我们如何通过 Node.js 来使用 gRPC 呢?流程如下
用 protocol buffer 编译器生成服务器和客户端代码
Node.js 的类库是在运行时加载
.proto
中的客户端存根并动态生成服务描述符。要加载一个
.proto
文件,只需要require
gRPC 类库,然后使用它的load()
方法server.js
client.js
详情请参考:node 调用 rpc 实例,提供运行时加载
.proto
与编译时实例。Thrift
Thrift 是一个跨语言的服务部署框架,最初由 Facebook 于 2007 年开发,2008年进入 Apache 开源项目。Thrift 通过 接口描述语言(Interface description language,缩写IDL)来定义 RPC 的接口和数据类型,然后通过编译器生成不同语言的代码(目前支持 C++, Java, Python, PHP, Ruby, Erlang, Perl, Haskell, C#, Cocoa, Smalltalk 和 OCaml),并由生成的代码负责 RPC 协议层 和 传输层 的实现。
利用 Thrift 用户只需要做三件事:
利用 IDL 定义数据结构及服务
利用代码生成工具将 IDL 编译成对应语言(如C++、JAVA),编译后得到基本的框架代码
利用编译后的基本框架代码,完成完整代码(纯C++代码、JAVA代码等)
Thrift 基础架构
Thrift 是 C/S 的架构体系,通过代码生成工具将 IDL 接口定义文件生成服务器端和客户端代码(可以为不同语言),从而实现服务端和客户端跨语言的支持。用户在 Thirft 描述文件中声明自己的服务,这些服务经过编译后会生成相应语言的代码文件,然后客户端调用服务、服务器端提供服务来实现服务。其中 protocol(协议层, 定义数据传输格式,可以为二进制或者XML等)和 transport(传输层,定义数据传输方式,可以为 TCP/IP 传输,内存共享或者文件共享等)被用作运行时库。
Thrift 协议栈如下图所示:
在 Client 和 Server 最顶层是用户自定义的处理逻辑,用户只需要编写用户逻辑,就可以完成整套的 RPC 调用流程。用户逻辑的下面是 Thrift Runtime 自动生成的代码,这些代码主要用于结构化数据的解析,发送和接收,同时服务器端的自动生成代码中还包含了RPC 请求的转发(Client 的A调用转发到Server A函数进行处理)。
TProtocol 层
TProtocol 主要负责结构化数据组装成 Message,或者从 Message 结构中读出结构化数据。TProtocol 将一个有类型的数据转化为字节流以交给 TTransport 进行传输,或者从 TTransport 中读取一定长度的字节数据转化为特定类型的数据。如 int32 会被 TBinaryProtocol Encode 为一个四字节的字节数据,或者 TBinaryProtocol 从 TTransport 中取出四个字节的数据 Decode 为 int32。
协议(TProtocol)层提供了序列化和反序列化。Thrift 提供了以下协议:
TTranport 层
传输(Transport)层是负责读写传输的。Thrift 支持以下几种:
TServer 层
TServer 负责接收 Client 的请求,并将请求转发到 Processor 进行处理。TServer 主要任务就是高效的接受 Client 的请求,特别是在高并发请求的情况下快速完成请求。Thrift 支持以下几种服务类型:
TSimpleServer:单线程服务器端使用的标准的阻塞式I/O
TThreadPoolServer:多线程服务器端使用标准的阻塞式I/O。
TNonblockingServer: 多线程服务器端使用的非阻塞式I/O,并实现了Java中的NIO通道。
TProcessor 层
TProcessor 负责对 Client 的请求做出响应,包括 RPC 请求转发,调用参数解析和用户逻辑调用,返回值写回等处理步骤。Processor 是服务器端从 Thrift 框架转入用户逻辑的关键流程,同时也负责向 Message 结构中写入数据或者读出数据。处理器的接口也很简单:
Underlying I/O 层
底层 IO 模块,负责实际的数据传输通信,包括 Socket,文件,或者压缩数据流等。
TTransport 负责以字节流方式发送和接收 Message,是底层 IO 模块在 Thrift 框架中的实现,每一个底层 IO 模块都会有一个对应 TTransport 来负责 Thrift 的字节流 (Byte Stream) 数据在该 IO 模块上的传输。例如 TSocket 对应 Socket 传输,TFileTransport 对应文件传输。
Thrift 数据类型
Thrift 通过一个中间语言 IDL(interface definition language,接口定义语言)来定义 RPC 的接口和数据类型。Thrift IDL类型系统由预定义的基本类型,用户定义的结构,容器类型,异常和服务定义组成,接下了解下:
Base Types(基本类型)
Containers(容器)
Thrift 容器是强类型的,可映射到大多数编程语言中常用的容器类型。使用 C++ 模板类来标注。有三种可用类型:
Structs(结构体)
Thrift 结构体定义了一个用在多种语言之间的通用对象。定义一个 Thrift 结构体的基本语法与 C 结构体定义非常相似。域可由一个整型域标识符(在该结构体的作用域内是唯一的),以及可选的默认值来标注。
Exceptions(异常)
异常在语法和功能上都与结构体相同,唯一的区别是使用 exception 关键词,而非 struct 关键词进行声明。 生成的对象继承自各目标编程语言中适当的异常基类,以便与任何给定语言中的本地异常处理无缝地整合。
Services(服务)
使用 Thrift 类型定义服务,对一个服务的定义在语法上等同于在面向对象编程中定义一个接口(或一个纯虚抽象类)。Thrift编译器生成实现该接口的客户与服务器存根。服务的定义如下:
使用 Thrift 类型来定义服务。服务的定义在语义上等同于 OOP 编程中定义的接口(或纯抽象类)。Thrift 编译器会生成实现这些接口的 client 和 server stub。
语法:
service <name> { <returntype> <name>(<arguments>) [throws (<exceptions>)] ... }
例子:
Typedefs(类型定义)
Thrift 支持 C / C ++ 类型定义风格。
引申
: C++ 风格指南 -- 类型命名Enums(枚举)
枚举创建一个带有命名值的枚举类型,枚举类型第一个元素默认为0,也可以为其赋值,但后一个元素必须比前面的元素大。赋予的任何常量值必须为非负数。
Namespaces(命名空间)
Thrift 的命名空间类似于 C++ 中的 namespace 和 java 中的 package,提供了一种隔离代码文件的方式,避免命名冲突等。
Comments(注释)
Thrift 支持 Java 中的多行注释
Thrift支持 shell、C 风格的多行以及 Java / C ++ 风格的单行注释。
Includes(引入)
为了提高代码可用性,是代码可复用,经常将不同类别的代码写在不同的文件中,将代码隔离开。include 使得来自另一个文件的所有符号都可见(带前缀使用),并将相应的 include 语句添加到此Thrift 文档生成的代码中。
Constants(常量)
Thrift 可以定义用于多种语言的常量,复杂类型和结构可使用 JSON 表示法指定。
Node.js 使用 Thrift
Thrift Javascript Library 官方推出的基于浏览器的 Apache Thrift 实现通过 XHR 和 WebSocket 在 Http [s] 协议上使用 JSON 协议来支持 RPC 的工具库。
安装
使用示例:
Other Resource
grpc
gRPC 官方文档中文版
gRPC官网
Thrift: The Missing Guide
thrift-parser 解析 IDL 语法数
序列化和反序列化
Thrift: Scalable Cross-Language Services Implementation
An incomplete guide to facebook thrift
Apache Thrift
Serialization
tutorial.thrift
Thrift RPC详解(转载)
Node —— RPC
6种微服务RPC框架,你知道几个?
The text was updated successfully, but these errors were encountered: