100字范文,内容丰富有趣,生活中的好帮手!
100字范文 > Node.js Eggjs和RPC框架的结合实践方案 使用egg-sofa-rpc模块搭建服务端和客户端

Node.js Eggjs和RPC框架的结合实践方案 使用egg-sofa-rpc模块搭建服务端和客户端

时间:2023-02-09 00:02:29

相关推荐

Node.js Eggjs和RPC框架的结合实践方案 使用egg-sofa-rpc模块搭建服务端和客户端

前言

本文将简单介绍RPC,以及通过Eggjs框架搭建RPC客户端和服务端。

过程遇到的问题记录在这:Node.js Eggjs使用RPC模块 egg-sofa-rpc 踩坑记录

RPC介绍

RPC ( Remote Procedure Call ) 即 远程过程调用,就是像调用本地的函数一样去调用远程的函数。简单讲,就是本地调用的逻辑处理的过程放在的远程的机器上,而不是本地服务代理来处理。

也就是说两台服务器A,B,一个应用部署在A服务器上,想要调用B服务器上应用提供的函数/方法,由于不在一个内存空间,不能直接调用,需要通过网络来表达调用的语义和传达调用的数据。

HTTP和RPC的对比

在HTTP和RPC的选择上,可能有些人是迷惑的,主要是因为,有些RPC框架配置复杂,如果走HTTP也能完成同样的功能,那么为什么要选择RPC,而不是更容易上手的HTTP来实现了。

以下是阐述HTTP和RPC的异同。

传输协议 RPC,可以基于TCP协议,也可以基于HTTP协议HTTP,基于HTTP协议 传输效率 RPC,使用自定义的TCP协议,可以让请求报文体积更小,或者使用HTTP2协议,也可以很好的减少报文的体积,提高传输效率HTTP,如果是基于HTTP1.1的协议,请求中会包含很多无用的内容,如果是基于HTTP2.0,那么简单的封装以下是可以作为一个RPC来使用的,这时标准RPC框架更多的是服务治理 性能消耗,主要在于序列化和反序列化的耗时 RPC,可以基于thrift实现高效的二进制传输HTTP,大部分是通过json来实现的,字节大小和序列化耗时都比thrift要更消耗性能 负载均衡 RPC,基本都自带了负载均衡策略HTTP,需要配置Nginx,HAProxy来实现 服务治理(下游服务新增,重启,下线时如何不影响上游调用者) RPC,能做到自动通知,不影响上游HTTP,需要事先通知,修改Nginx/HAProxy配置

总结:

RPC主要用于公司内部的服务调用,性能消耗低,传输效率高,服务治理方便。HTTP主要用于对外的异构环境,浏览器接口调用,APP接口调用,第三方接口调用等。

HTTP接口是在接口不多、系统与系统交互较少的情况下,解决信息孤岛初期常使用的一种通信手段;优点就是简单、直接、开发方便。利用现成的HTTP协议进行传输。但是如果是一个大型的网站,内部子系统较多、接口非常多的情况下,RPC框架的好处就显示出来了,首先就是长链接,不必每次通信都要像HTTP一样去3次握手什么的,减少了网络开销;其次就是RPC框架一般都有注册中心,有丰富的监控管理;发布、下线接口、动态扩展等,对调用方来说是无感知、统一化的操作。第三个来说就是安全性。最后就是最近流行的服务化架构、服务化治理,RPC框架是一个强力的支撑

准备工作

本文以windows为例,其他操作系统的安装、使用方法请自行 google。

安装 nodejs

要求版本 >= 8.0.0

下载安装包:/zh-cn/执行安装

安装zookeeper

下载安装包:/releases.html

如果是最新版本则下载带有bin的包,不带bin是源码,需要自行编译

启动zookeeper服务

复制conf/zoo_sample.cfgconf/zoo.cfg

在bin目录打开cmd,执行命令运行服务

Linux:bin/zkServer.sh startWindows:bin/zkServer.cmd

全局安装egg-init

$ npm i egg-init -g

初始化工程

通过egg-init初始化项目脚手架,选择 simple 模板,接下来根据实际情况填写必要信息

$ egg-init? Please select a boilerplate type (Use arrow keys)──────────────❯ simple - Simple egg app boilerplatets - Simple egg && typescript app boilerplateempty - Empty egg app boilerplateplugin - egg plugin boilerplateframework - egg framework boilerplate

进入生成好的项目目录,并安装依赖

$ cd /rpc-demo$ npm i

安装sofa-node框架,egg-sofa-rpc插件和egg-rpc-generator工具

$ npm i sofa-node --save$ npm i egg-sofa-rpc --save$ npm i egg-rpc-generator --save-dev

配置RPC

配置

配置package.json的 scripts 节点,增加一个命令 rpc 如下

{"scripts": {"rpc": "egg-rpc-generator -p protobuf"}}

在使用egg-rpc-generator生成代理文件时,会同时进行ProtoRPCPlugin,jsdoc2jar,Jar2ProxyPlugin这三种模式,其中会编译生成jar包,在目前的实际应用中,需要用的是egg之间调用,不涉及跨平台,那也就没必要生成相关jar包,解决方式是添加参数-p

配置package.json的 egg 节点,增加"framework": "sofa-node"

{"egg": {"framework": "sofa-node","declarations": true},}

配置config/plugin.js开启egg-sofa-rpc插件

// config/plugin.js'use strict';module.exports = {sofaRpc: {enable: true,package: 'egg-sofa-rpc',},};

默认的服务发现依赖于 zookeeper,所以需要配置一个 zk 的地址。在config/config.{env}.js中配置rpc如下:

'use strict';module.exports = appInfo => {const config = exports = {};config.rpc = {registry: {address: '127.0.0.1:2181', // zk 地址指向本地 2181 端口},};return config;};

定义接口文件

这个proto文件是作为通信转换,在客户端或服务端都需要。

在 egg 项目根目录下创建 proto 目录,创建 ProtoService.proto 文件

.├── app│ ├── controller│ │ └── home.js│ └── router.js├── config│ ├── config.default.js│ └── plugin.js├── package.json└── proto└── ProtoService.proto

protobuf 有自己的接口定义语言,详细可以参考官方文档。

# ProtoService.proto syntax = "proto3";package com.nodejs.rpc;option java_multiple_files = false; // 可选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.nodejs.rpc.ProtoService,它有一个叫echoObj的方法,入口参数类型是EchoRequest,返回值类型是EchoResponse

RPC服务端

配置 RPC 服务端的参数

通过config/config.{env}.js配置 RPC 服务端server的参数

// config/config.default.js'use strict';module.exports = appInfo => {const config = exports = {};config.rpc = {registry: {address: '127.0.0.1:2181', // zk 地址指向本地 2181 端口},server: {namespace: 'com.nodejs.rpc',},};return config;};

其中最主要的配置就是 namespace,其他配置都可以缺省:

namespace(必选): 接口的命名空间,所有的暴露的接口默认都在该命名空间下selfPublish(可选): 是否每个 worker 进程独立暴露服务。nodejs 多进程模式下,如果多个进程共享一个端口,在 RPC 这种场景可能造成负载不均,所以 selfPublish 默认为 true,代表每个进程独立监听端口和发布服务port(可选): 服务监听的端口(注意:在 selfPublish=true 时,监听的端口是基于这个配置生成的)maxIdleTime(可选): 客户端连接如果在该配置时长内没有任何流量,则主动断开连接responseTimeout(可选): 服务端建议的超时时长,具体的超时还是以客户端配置为准codecType(可选): 推荐的序列化方式,默认为 protobuf

实现接口逻辑

app/rpc目录下创建 ProtoService.js 文件,用于实现接口逻辑

'use strict';exports.echoObj = async function(req) {return {code: 200,message: 'hello ' + req.name + ', you are in ' + req.group,};};

发布服务

运行命令npm run dev

测试 RPC 接口

在单元测试中,我们可以通过app.rpcRequest接口来方便的测试我们自己暴露的 RPC 服务,例如:

'use strict';const { app, assert } = require('egg-mock/bootstrap');describe('test/app/rpc/ProtoService.test.js', () => {it('should assert', () => {const pkg = require('../../../package.json');assert(app.config.keys.startsWith(pkg.name));// const ctx = app.mockContext({});// yield ctx.service.xx();});it('should invoke ProtoService', done => {app.rpcRequest('com.nodejs.rpc.ProtoService').invoke('echoObj').send({ name: 'test', group: 'A' }).expect({ code: 200, message: 'hello test, you are in 0' }, done);});});

详细app.rpcRequest的 api 可以参考:单元测试 RPC 服务的方法

执行单元测试需要先配置要调用的接口

RPC客户端

配置 RPC 客户端的参数

${app_root}/config/config.${env}.js做一些全局性的配置

// ${app_root}config/config.${env}.jsexports.rpc = {client: {responseTimeout: 3000,},};

responseTimeout(可选): RPC 的超时时长,默认为 3 秒

配置要调用的接口

RPC 客户端还有一个重要的配置文件是:${app_root}/config/proxy.js,你需要把你调用的服务配置到里面,然后通过egg-rpc-generator工具帮你生成本地调用代码。

让我们看一个最简单的配置,它的基本含义是:我需要调用sofarpc应用暴露的com.nodejs.rpc.ProtoService这个服务。

'use strict';module.exports = {services: [{appName: 'sofarpc',api: {ProtoService: 'com.nodejs.rpc.ProtoService',},}],};

appName(必选): 服务提供方的应用名,如果没有可以任意起一个api(必选): 接口列表,是一个 key-value 键值对,key 是生成的 proxy 文件名,value 是接口名(如果要更精细的配置也可以是一个对象)

详细的配置可以参考 RPC 代理(Proxy)配置

生成本地调用代理

在根目录下运行npm run rpc,生成调用的 proxy 文件

$ npm run rpc> rpc_server@1.0.0 rpc /egg-rpc-demo> egg-rpc-generator -p protobuf[EggRpcGenerator] framework: /egg-rpc-demo/node_modules/sofa-node, baseDir: /egg-rpc-demo[ProtoRPCPlugin] found "com.nodejs.rpc.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.rpcClient.createConsumer({interfaceName: 'com.nodejs.rpc.ProtoService',targetAppName: 'sofarpc',version: '1.0',group: 'SOFA',proxyName: 'ProtoService',});if (!consumer) {// `app.config['sofarpc.rpc.service.enable'] = false` will disable this consumerreturn;}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,});}}return ProtoService;};/* eslint-enable */

调用服务

上面定义的这个 ProtoService 这个类,会挂载在 app.proxyClasses 上。

通过ctx.proxy.proxyName(注意这里是小驼峰)来访问生成的 proxy 代码,proxyName 就是上面 proxy.js 配置的 api 键值对中的 key。例如:上面配置的 ProtoService,但是需要特别注意的是 proxyName 会自动转成小驼峰形式,所以就是ctx.proxy.protoService

下面我们在 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;

和调用本地方法体验一模一样。

相关资源链接

egg-sofa-rpc 插件

/eggjs/egg-sofa-rpc

egg-rpc-generator 工具

/eggjs/egg-rpc-generator

zookeeper

/

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。