White's Studio.

Eggjs使用sofa-rpc-node

2019/02/20 Share

Eggjs使用sofa-rpc-node

最近读了比较多的 rpc 资料,目前所使用的项目为 node.js 项目,于是希望能在项目中使用 rpc 。

RPC (Remote Procedure Call)

RPC 即 远程过程调用,就是像调用本地的函数一样去调用远程的函数。

与 HTTP 的区别?

其实 HTTP 也可以实现远程调用的效果,那么 HTTP 与 RPC 到底是什么关系呢?有什么区别呢?

RPC

经过了解,我发现RPC 和 HTTP 其实是不在同一个层级的概念。

RPC 通常所讲是一个框架

这是阿里对 RPC 开源RPC框架sofa 的优点截图

RPC 也可以基于 HTTP 实现,如上面的 GRPC ,就是 google 基于 HTTP2.0协议

gRPC is a modern open source high performance RPC framework that can run in any environment. It can efficiently connect services in and across data centers with pluggable support for load balancing, tracing, health checking and authentication. It is also applicable in last mile of distributed computing to connect devices, mobile applications and browsers to backend services.

RPC 也可以基于 TCP,Sockets实现

HTTP (HyperText Transfer Protocol)

而 HTTP 是基于 TCP 的实现的超文本传输协议,HTTP 是无状态协议;最初用于浏览器与服务器的通信,后来广泛用于各个服务间的通信。

区别

RPC远程过程调用RPC框架可以的通信过程可以使用各种通信协议(如 HTTP,TCP以及各种自定义协议)实现。

简单来说成熟的rpc库相对http容器,更多的是封装了“服务发现”,”负载均衡”,“熔断降级”一类面向服务的高级特性。可以这么理解,rpc框架是面向服务的更高级的封装。如果把一个http servlet容器上封装一层服务发现和函数代理调用,那它就已经可以做一个rpc框架了。

所以为什么要用rpc调用?因为良好的rpc调用是面向服务的封装,针对服务的可用性和效率等都做了优化。单纯使用http调用则缺少了这些特性。

From: 手不要乱摸


查阅了一下资料 ,nodejs有哪些比较流行的 rpc 框架?

  1. grpc —— https://grpc.io, 这个是国外比较流行的,有 google 背书,支持多语言,听说使用的公司也比较多,看上去是比较成熟的框架。
  2. sofa —— https://tech.antfin.com/sofa 这个是国内阿里开源的,目前阿里开源的 Eggjs 框架也开源了基于 sofa 的最佳实践。
  3. DUBBO —— 阿里开源的 java RPC 框架

实践

本人基于“Eggjs 和 SOFA 的跨语言互调” 也尝试了一下 Eggjs 下的 rpc 调用

zookeeper

sofa-rpc-node 的 rpc 是需要 zookeeper

zookeeper 是做什么的?为什么需要 zookeeper?

回到最初看 RPC 的原理, Client 端没有向 Server 端发起通信,而是需要Client Stub 和 Server Stub的桥接,完成 Client 和 Server 的通信;

问题来了:

  1. Client Stub 和 Server Stub的桥接过程中如何找到对应的 Server?(服务地址储存
  2. Server 接收到请求后,Client如何接收处理结果?(服务状态感知

Zookeeper 在此实现的框架上的作用就是解决上面两个问题?具体解答可以参考详情

PS:对比起 http 服务里面,我们有一个配套的支撑基础组件叫做DNS,其根据域名找到某几个外网ip地址。然后,请求打到网站内部,一般首先到nginx群,nginx也会根据url规则找到配置好的一组ip地址,此外,nginx根据healthcheck来检查http服务是否可用。

Zookeeper 在 解决服务地址储存的所做事情就如 DNS 和 nginx 一般

接下来主要从 “node服务 ——> Java 服务“ 和 “ Java 服务 ——> node服务” (使用sofa调用)来尝试

一. 配置

  • 通过 egg-init 初始化项目脚手架,选择 simple 模板,接下来根据实际情况填写必要信息
1
2
3
4
5
6
7
8
9
$ 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
  • 进入生成好的项目目录,并安装依赖

  • 安装 egg-sofa-rpc 插件和 egg-rpc-generator 工具

1
2
$ npm i egg-sofa-rpc --save
$ npm i egg-rpc-generator --save-dev
  • 配置 package.json 的 scripts 节点,增加一个命令 "rpc": "egg-rpc-generator"
1
2
3
4
5
6
{
"scripts": {
"dev": "egg-bin dev",
"rpc": "egg-rpc-generator"
}
}

另一点阿里的文档没有提到的很坑的一点,必须在package.json文件最外层添加,这个egg-int工具并不会帮建这个

1
2
3
"egg": {
"framework": "sofa-node"
}
  • 配置 config/plugin.js 开启 egg-sofa-rpc 插件
1
2
3
4
5
6
// config/plugin.js

exports.sofaRpc = {
enable: true,
package: 'egg-sofa-rpc',
};
  • config/config.default.js 配置zookeeper
1
2
3
4
5
6
7
8
// config/config.default.js
'use strict';

exports.sofaRpc = {
registry: {
address: '127.0.0.1:2181', // zk 地址指向本地 2181 端口
},
};

二. 配置接口

ProtoService.proto

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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 文件定义了一个服务:实际上就是需要调用 Java 项目中com.alipay.sofa.rpc.protobuf.ProtoService,它有一个叫 echoObj 的方法,入口参数类型是 EchoRequest,返回值类型是 EchoResponse

该 Java 项目在此 Eggjs RPC Example

protobuf

protobuf 是什么?

Protocol Buffers 是一种轻便高效的结构化数据存储格式,可以用于结构化数据串行化,或者说序列化。它很适合做数据存储或 RPC 数据交换格式。可用于通讯协议、数据存储等领域的语言无关、平台无关、可扩展的序列化结构数据格式。目前提供了 C++、Java、Python 三种语言的 API

用于序列化

  • config/proxy.js 中配置要调用的服务信息
1
2
3
4
5
6
7
8
9
10
'use strict';

module.exports = {
services: [{
appName: 'sofarpc',
api: {
ProtoService: 'com.alipay.sofa.rpc.protobuf.ProtoService',
},
}],
};
  • 在根目录下运行 npm run rpc,生成调用的 proxy 文
1
2
3
4
5
6
7
8
$ 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"

Egg-sofa-generator

这个东西做了什么?

实际上就是生成了app/proxy/ProtoService.js - 调用服务的代理文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
// 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 */

调用

  • 在路由里通过 ctx.proxy去调用该方法就好
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 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;
1
npm run dev

调试结果 调用接口

Nodejs 暴露 RPC 服务给 Java 调用

Eggjs 和 SOFA 的跨语言互调 教程详情

这个可以自己去尝试下,目前不在此介绍

参考资料:

Eggjs 和 SOFA 的跨语言互调

nodejs有哪些比较流行的 rpc 框架?

深入浅出RPC原理

既然有 HTTP 请求,为什么还要用 RPC 调用?

分布式架构核心RPC原理

https://zhuanlan.zhihu.com/p/29857744

上文demo

End :)

CATALOG
  1. 1. Eggjs使用sofa-rpc-node
    1. 1.0.1. RPC (Remote Procedure Call)
  2. 1.1. 与 HTTP 的区别?
  3. 1.2. RPC
    1. 1.2.1. HTTP (HyperText Transfer Protocol)
    2. 1.2.2. 区别
  • 2. 实践
    1. 2.1. zookeeper
      1. 2.1.1. 一. 配置
      2. 2.1.2. 二. 配置接口
    2. 2.2. protobuf
    3. 2.3. Egg-sofa-generator
      1. 2.3.1. 调用
    4. 2.4. Nodejs 暴露 RPC 服务给 Java 调用
    5. 2.5.
      1. 2.5.1. End :)