SOFARPC 是近期蚂蚁金服开源的一个高可扩展性、高性能、生产级的 Java RPC 框架。在蚂蚁金服 SOFARPC 已经经历了十多年及五代版本的发展。SOFARPC 致力于简化应用之间的 RPC 调用,为应用提供方便透明、稳定高效的点对点远程服务调用方案。为了用户和开发者方便的进行功能扩展,SOFARPC 提供了丰富的模型抽象和可扩展接口,包括过滤器、路由、负载均衡等等。
伴随 SOFARPC 的开源,我们也开源了 sofa-bolt-node 和 sofa-rpc-node 两个 Nodejs RPC 基础模块。但细心的用户可能注意了我们在文档里面写到并不希望大家直接使用它们,并预告会在 Eggjs 里提供 RPC 最佳实践。
现在这个最佳实践来了,它就是:
egg-sofa-rpc 插件:
https://github.com/eggjs/egg-sofa-rpc
egg-rpc-generator 工具:
https://github.com/eggjs/egg-rpc-generator
本文通过 Step by Step 的形式介绍了 Eggjs 和 SOFA(Java)是如何进行互联互通的,涵盖了 RPC 的服务发现、接口定义、本地代理生成、服务端实现等各方面,期望展现给你一个相对完整的 Nodejs RPC 解决方案。考虑到社区的接受度、多语言友好性等因素,接下来的示例采用 protobuf 作为 RPC 的序列化方式。
准备工作
注意: 本文以 macOS 为例,其他操作系统的安装、使用方法请自行 google。
安装 nodejs >= 8.0.0
-
-
下载安装包:
https://nodejs.org/en/download/
-
执行安装
-
安装 zookeeper
$ brew install zookeeper
启动 zookeeper
服务
$ zkServer startZooKeeper JMX enabled by defaultUsing config: /usr/local/etc/zookeeper/zoo.cfgStarting zookeeper ... STARTED
克隆 SOFARPC Java 的示例仓库
SOFARPC 的更多信息可以参考官方文档
git clone git@github.com:gxcsoccer/sofa-rpc-java-demo.git
安装 egg-init
$ npm i egg-init -g
创建工程
通过 egg-init
初始化项目脚手架,选择 simple 模板,接下来根据实际情况填写必要信息
$ egg-init? Please select a boilerplate type (Use arrow keys) ────────────── simple - Simple egg app boilerplate ts - Simple egg && typescript app boilerplate empty - Empty egg app boilerplate plugin - egg plugin boilerplate framework - egg framework boilerplate
进入生成好的项目目录,并安装依赖
$ cd /rpc-demo$ npm i
安装 egg-sofa-rpc
插件和 egg-rpc-generator
工具
$ npm i egg-sofa-rpc --save$ npm i egg-rpc-generator --save-dev
配置 package.json
的 scripts 节点,增加一个命令 rpc 如下
{ "scripts": { "start": "egg-scripts start --daemon --title=egg-server-rpc-demo", "stop": "egg-scripts stop --title=egg-server-rpc-demo", "dev": "egg-bin dev", "debug": "egg-bin debug", "test": "npm run lint -- --fix && npm run test-local", "test-local": "egg-bin test", "cov": "egg-bin cov", "lint": "eslint .", "ci": "npm run lint && npm run cov", "autod": "autod", "rpc": "egg-rpc-generator" }
}
配置 config/plugin.js
开启 egg-sofa-rpc
插件
// config/plugin.js
exports.sofaRpc = { enable: true, package: 'egg-sofa-rpc'
};
定义接口
protobuf 有自己的接口定义语言,详细可以参考官方文档。
# ProtoService.proto syntax = "proto3";package com.alipay.sofa.rpc.protobuf;option java_multiple_files = true; // 可选option java_outer_classname = "ProtoServiceModels"; // 可选service ProtoService { rpc echoObj (EchoRequest) returns (EchoResponse) {}}message EchoRequest { string name = 1; Group group = 2;}message EchoResponse { int32 code = 1; string message = 2;}enum Group { A = 0; B = 1;}
上面这个 ProtoService.proto 文件定义了一个服务:com.alipay.sofa.rpc.protobuf.ProtoService
,它有一个叫 echoObj
的方法,入口参数类型是 EchoRequest
,返回值类型是 EchoResponse
。
调用Java暴露的RPC服务
1、启动 Java 服务端
进入上面克隆的 Java 示例仓库,运行 ProtobufServiceServerMain
2、配置服务发现参数
我们默认的服务发现依赖于 zookeeper,所以需要配置一个 zk 的地址。在 config/config.{env}.js
中配置如下:
// config/config.default.js
'use strict';
exports.sofaRpc = { registry: { address: '127.0.0.1:2181', // zk 地址指向本地 2181 端口 }
};
3、获取接口定义
在 egg 项目根目录下创建 proto 目录,然后将上面定义的 ProtoService.proto 文件放到里
.├── app│ ├── controller│ │ └── home.js│ └── router.js├── config│ ├── config.default.js│ └── plugin.js├── package.json└── proto └── ProtoService.proto
4、配置要调用的接口
在 config/proxy.js
中配置要调用的服务信息
'use strict';
module.exports = { services: [{ appName: 'sofarpc', api: { ProtoService: 'com.alipay.sofa.rpc.protobuf.ProtoService', } }]
};
appName
(必选): 服务提供方的应用名,如果没有可以任意起一个api
(必选): 接口列表,是一个 key-value 键值对,key 是生成的 proxy 文件名,value 是接口名(如果要跟精细的配置也可以是一个对象)
config/proxy.js
详细的配置说明可以参考文档
5、生成调用代理
在根目录下运行 npm run rpc
,生成调用的 proxy 文件
$ npm run rpc
> rpc-demo@1.0.0 rpc /egg-rpc-demo
> egg-rpc-generator
[EggRpcGenerator] framework: /egg-rpc-demo/node_modules/egg, baseDir: /egg-rpc-demo
[ProtoRPCPlugin] found "com.alipay.sofa.rpc.protobuf.ProtoService" in proto file
[ProtoRPCPlugin] save all proto info into "/egg-rpc-demo/run/proto.json"
运行成功以后,会发现生成了两个文件
app/proxy/ProtoService.js
- 调用服务的代理文件run/proto.json
- 从 .proto 文件中导出的接口信息,是一个 json 格式文件
├── app│ ├── controller│ │ └── home.js│ ├── proxy│ │ └── ProtoService.js│ └── router.js├── config│ ├── config.default.js│ ├── plugin.js│ └── proxy.js├── package.json├── proto│ └── ProtoService.proto└── run └── proto.json
生成的 app/proxy/ProtoService.js
文件内容如下(注意:不要手动去改这个文件):
// Don't modified this file, it's auto created by egg-rpc-generator
'use strict';
const path = require('path');
/* eslint-disable */
/* istanbul ignore next */
module.exports = app => { const consumer = app.sofaRpcClient.createConsumer({ interfaceName: 'com.alipay.sofa.rpc.protobuf.ProtoService', targetAppName: 'sofarpc', version: '1.0', group: 'SOFA', proxyName: 'ProtoService', responseTimeout: 3000, }); if (!consumer) { // `app.config['sofarpc.rpc.service.enable'] = false` will disable this consumer return; } app.beforeStart(async() => { await consumer.ready(); }); class ProtoService extends app.Proxy { constructor(ctx) { super(ctx, consumer); } async echoObj(req) { return await consumer.invoke('echoObj', [ req ], { ctx: this.ctx,codecType: 'protobuf', }); } } return ProtoService;
};
/* eslint-enable */
6、调用代理类,实现业务逻辑
上面定义的这个 ProtoService 这个类,会挂载在 app.proxyClasses 上。通过 ctx.proxy.protoService(注意这里是小驼峰)可以访问它的实例,这样我们就可以在业务中调用 RPC 的服务了,例如:下面我们在 home controller 调用 ProtoService 的 echoObj 方法
// app/controller/home.js
'use strict';
const Controller = require('egg').Controller;
class HomeController extends Controller { async index() { const { ctx } = this; const res = await ctx.proxy.protoService.echoObj({ name: 'gxcsoccer', group: 'A', }); ctx.body = res; }
}
module.exports = HomeController;
7、启动应用,调试
$ npm run dev
在浏览器中访问 http://127.0.0.1:7001/
,得到下面的结果,说明成功了
暴露RPC服务给Java调用
_____
这回换做 Nodejs 来暴露同样的服务,Java 端作为消费者
1、配置服务发现参数
和上面作为调用者的配置一样
// config/config.default.js
'use strict';
exports.sofaRpc = { registry: { address: '127.0.0.1:2181', // zk 地址指向本地 2181 端口 }
};
2、定义接口
同样需要先定义接口,然后将 .proto 文件放到 proto
目录下,然后运行 npm run rpc
,这些和上面作为调用者时都一样
3、配置 RPC 服务端的参数
通过 config/config.{env}.js
配置 RPC 服务端的参数
// config/config.default.js
'use strict';
exports.sofaRpc = { server: { namespace: 'com.alipay.sofa.rpc.protobuf' }
};
其中最主要的配置就是 namespace,其他配置都可以缺省:
namespace
(必选): 接口的命名空间,所有的暴露的接口默认都在该命名空间下selfPublish
(可选): 是否每个 worker 进程独立暴露服务。nodejs 多进程模式下,如果多个进程共享一个端口,在 RPC 这种场景可能造成负载不均,所以 selfPublish 默认为 true,代表每个进程独立监听端口和发布服务port
(可选): 服务监听的端口(注意:在 selfPublish=true 时,监听的端口是基于这个配置生成的)maxIdleTime
(可选): 客户端连接如果在该配置时长内没有任何流量,则主动断开连接responseTimeout
(可选): 服务端建议的超时时长,具体的超时还是以客户端配置为准codecType
(可选): 推荐的序列化方式,默认为 protobuf
4、实现接口逻辑
在 app/rpc
目录下创建 ProtoService.js 文件,用于实现接口逻辑
'use strict';
exports.echoObj = async function(req) { return { code: 200, message: 'hello ' + req.name + ', you are in ' + req.group, };
};
5、启动应用,发布服务
$ npm run dev
6、Java 作为客户端调用服务
进入上面克隆的 Java 示例仓库,运行 ProtobufServiceClientMain
执行的结果如下:
Sofa-Middleware-Log SLF4J : Actual binding is of type [ com.alipay.sofa.rpc Log4j2 ]
原文发布时间为:2018-06-21
本文作者:宗羽