in Architecture

在 Node.js 中提供 gRPC 服务

引子

Web APP 的大行其道导致了 API 架构的流行,大量站点及服务使用基于 HTTP 1.1 的API 进行交互。这一类文本传输型 API 的优点很突出:易于编写和理解;支持异构平台的沟通。缺点也很明显:基于文本从而导致API传输内容过于庞大;存在客户端易感知的延迟。

如果对性能有所要求,不妨试试基于二进制传输的RPC框架,比如:

gRPC

gRPC 是一个高性能、开源的、通用的、面向移动端的 RPC 框架,传输协议基于 HTTP/2,这意味着它支持 双向流、流控、头部压缩、单 TCP 连接上的请求多路复用 等特性。

接口层面,gRPC默认使用 Protocol Buffers (简称 protobuf)做为其接口定义语言(IDL)来描述其服务接口和负载消息的格式。

grpc-accross-lang-and-platforms

gRPC目前提供的语言支持有:C++, Node.js, Python, Ruby, Objective-C, PHP, C#。

与 Node.js 集成

接口定义

protobuf 做为IDL的特点是语义良好、数据类型定义完备,当前语言版本分为 proto2proto3 。

protobuf 的接口定义大致分为几个部分:

  1. IDL版本 proto2/3
  2. 包名字
  3. 服务定义 和 方法定义
  4. 消息定义:请求消息和响应消息

此处,我们用 proto3 定义一个 testPackage 包中的 testService 服务,它仅提供一个 ping 方法,返回结果放在 message 字段中:

Demo 版本

服务端

以 Node.JS 为例,我们使用 grpc 包,以动态加载 .proto 方式来提供 RPC 服务:

需要注意的是:此处的代码是在内网测试,使用的是 ServerCredentials.createInsecure() 创建的非安全连接。如果是在公网提供 RPC 服务,请参考鉴权手册选择适合的方案: http://www.grpc.io/docs/guides/auth.html 

客户端

客户端亦使用动态加载 .proto 接口定义的方式来进行服务调用(暗含的意思是,这个 .proto 文件需要服务端和客户端同时拥有,并且版本要一致,如果接口有升级,要确保双方拥有的  .proto 版本能同步更新):

 

优化版本

上边的Demo版本仅是个可运行的玩具,在工程实践中,我们会有一些额外的需求:

  • .proto 文件置于指定文件夹,自动加载
  • 包和服务名称不需要硬编码,全由调用端动态指定
  • 可同时暴露/调用 多个包的多个RPC端点
  • ……

基于此,有了优化后的动态版本:

服务端

rpcServer.js:

server.js 这么用它:

客户端

rcpClient.js:

业务调用示例:

单元测试

测试结果为单请求毫秒级响应:

当然,这种顺序执行的RPC测试用例并不能准确地反映并发性能,仅供参考。

打赏作者
您的支持将激励我继续创作!

您的支持将鼓励我们继续创作!

[微信] 扫描二维码打赏

[支付宝] 扫描二维码打赏

Write a Comment

Comment

15 Comments

  1. 请问您有将grpc应用于正式环境吗,我在应用过程中碰到一个问题,我有4个模块,全部都是同一个客户端连接到同一个服务端(两台机器,一台客户端,一台服务端),但其中一个模块的请求在某些情况下(不明什么情况)会阻塞那一个模块的rpc请求,一开始以为是超时过长,后面加上deadline也一样无法解决问题,进入阻塞后该模块的所有请求都会deadline,log上看到的就全都是deadline。但在同时,其他模块的请求确实正常的。如果要正常的话要么就得重启客户端(重启服务端没有用),或者隔上一段时间,大约须隔20~30分钟左右才会恢复。
    这个问题困扰了我许久,求指教

  2. 打扰了!请问一下, server.bind 在本地好使, 然后在阿里云ecs 上就跑不起来了..bind 返回的结果是0, 启动失败. 可能是什么问题导致的呢? 目前没有用tsl方式启动.