在使用Nodejs开发RPC服务之前,首先先要了解一下什么是RPC协议。
RPC(Remote Procedure Call,远程过程调用)是一种通过网络从远程计算机请求服务的协议,通俗来讲,一次RPC调用就是,客户端A把需要请求的服务名,以及服务对应的参数通过网络传到服务端,由服务端执行服务后,将结果通过网络返回给客户端的过程。
如下图所示(图片源自网络),一个完整的RPC架构里面包含了四个核心的组件,分别是Client ,Server,Client Stub以及Server Stub。
名称 | 作用 |
---|---|
client | 服务的调用方。 |
server | 真正的服务提供者。 |
client stub | 存放服务端的地址消息,再将客户端的请求参数打包成网络消息,然后通过网络远程发送给服务方。 |
server stub | 接收客户端发送过来的消息,将消息解包,并调用本地的方法。 |
由于RPC协议是基于TCP协议的,所以相比于我们常用的HTTP协议,RPC协议能够更快速,更稳定地提供服务。而这些特性,在一些使用serverless的场景下(在大公司serverless的场景居多),就变得非常有优势了。
举例来说,某大型互联网公司需要打通所有的内部系统的登录功能,开发一个“统一登录平台”。那么在这种场景下,不论是从服务效率、服务稳定性,还是从代码可接入性、代码可维护性的角度看,使用RPC开发“统一登录平台”相关的服务,都比使用HTTP协议方便得多。
在网络上,有着各种各样的开源的RPC框架,这些RPC框架把我们从“直接操作TCP协议中传输的buffer”中解放出来,能够较为方便地搭建RPC服务。
下面是一些比较常用的RPC框架的对比(图片源自网络):
各个框架的具体区别就不在这里介绍了,各位可以百度一下。
我们直接来关注主题,如果要使用Nodejs开发RPC服务的话,使用thrift是一个不错的选择。它既提供了跨语言的支持,又拥有着不错的性能,而且序列化方式是支持二进制和json的,非常适合使用Nodejs进行开发。
首先贴一下thrift的官网及官方文档:点击这里
在这里我们可以找到thrift框架的详细介绍,以及使用Nodejs结合thrift框架开发RPC服务的相关信息。
Thrift是Facebook开源的一种高效的、支持多种编程语言的RPC的框架。它采用接口描述语言定义并创建服务,支持可扩展的跨语言服务开发,所包含的代码生成引擎可以在多种语言中(如 C++, Java, Python, PHP, Ruby, Erlang, Perl, Haskell, C#, Cocoa, Smalltalk 等)创建高效的服务,其传输数据采用二进制格式,相对 XML 和 JSON 体积更小,对于高并发、大数据量和多语言的环境更有优势。
操作系统 | 安装方式 |
---|---|
windows | 官网直接下载安装即可 |
macos | 推荐使用brew安装,终端执行brew install thrift |
thrift通过使用“接口描述语言”来编写跨语言的RPC服务规范文件,然后在终端使用thrift命令将这些服务规范文件编译成对应语言的代码文件。
举个例子,我们生成一个user.idl
文件,这个文件定义了User的结构,以及UserService提供的两个方法。
struct User{
1: string id,
2: string name,
3: i16 age,
}
service UserService{
void addUser(1: User user),
User getUser(1: string id),
}
然后使用thrift --gen js:node user.idl
命令,将这个文件编译为js文件。
我们可以看到编译出了两个js文件,user_types.js定义了User的类型,而UserService.js则提供了addUser和getUser的方法。
thrift的接口描述语言支持以下几种数据类型:
类型 | 描述 |
---|---|
bool | 布尔值 |
byte | 8位有符号整数 |
i16 | 16位有符号整数 |
i32 | 32位有符号整数 |
i64 | 64位有符号整数 |
double | 64位浮点数 |
string | UTF-8编码的字符串 |
binary | 二进制串 |
struct | 定义的结构体对象 |
list | 有序元素列表 |
set | 无序无重复元素集合 |
map | 有序的key/value集合 |
exception | 异常类型 |
service | 具体对应服务的类 |
通过使用这些数据类型,我们可以在idl文件中实现定义结构体对象,定义服务,定义异常的功能,并可以将idl文件编译成对应语言的源代码。
第一步,编写一个user.idl文件:
struct User{
1: string id,
2: string name,
3: i16 age,
}
service UserService{
void addUser(1: User user),
User getUser(1: string id),
}
第二步,使用thrift --gen js:node user.idl
命令,将这个文件编译为js文件。
第三步,使用npm install thrift --save
安装nodejs的thrift依赖(我安装的是0.12.0版本)
第四步,编写RPC客户端(client.js)文件和服务端(server.js)文件:
client.js
const thrift = require('thrift');
const UserService = require('./gen-nodejs/UserService.js');
const ttypes = require('./gen-nodejs/user_types');
const connection = thrift.createConnection('localhost', 7911);
const client = thrift.createClient(UserService, connection);
connection.on('error', function (err) {
console.error(err);
});
function addUser(user) {
client.addUser(user, function (err) {
if (err) {
console.error(err);
} else {
console.log("user added", user.id);
}
});
}
function getUser(userId) {
client.getUser(userId, function (err, response) {
if (err) {
console.error(err);
} else {
console.log("get user", response);
}
})
}
const user = new ttypes.User({
id: '2',
name: "shadowingszy",
age: 22
});
addUser(user)
getUser('2')
server.js
const thrift = require('thrift');
const UserService = require('./gen-nodejs/UserService.js');
const users = {};
const server = thrift.createServer(UserService, {
addUser: function (user, result) {
users[user.id] = user
console.log("[ADD_USER] add:", user, " current:", users)
result(null)
},
getUser: function (id, result) {
console.log("[GET_USER] get:", id)
result(null, users[id])
}
});
server.listen(7911);
console.log('server start');
第五步,目前的目录结构如下:
然后先执行node server.js
启动服务端程序。
然后执行node client.js
启动客户端程序。