Node.js与RPC 的实践方案-Eggjs使用sofa-rpc-node模块

1、前言

SOFARPC 是蚂蚁金服开源的一个高可扩展性、高性能、生产级的 Java RPC 框架,提供了丰富的模型抽象和可扩展接口,包括过滤器、路由、负载均衡等等,致力于简化应用之间的 RPC 调用,为应用提供方便透明、稳定高效的点对点远程服务调用方案。java

egg.js做为一个成熟的开源项目,清晰的定义了从配置、路由、扩展、中间件到控制器、定时任务等各个 Web 应用研发过程当中一些最基础的概念,这样不一样团队的开发者使用框架写出来的代码风格会更一致,接手老项目的上手成本也会更低。node

本文将简单介绍egg.js和sofa(Java)的相互做用,利用sofarpc的开源,展示一个初步的node.js RPC 解决方案。nginx

2、RPC介绍

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

其实 HTTP 也能够实现远程调用的效果,那么 HTTP 与 RPC 究竟是什么关系呢?有什么区别呢?github

通过了解,咱们发现RPC 和 HTTP 实际上是不在同一个层级的概念。typescript

(一)RPC与HTTP区别

RPC 一般所讲是一个框架npm

结合上图,阿里对RPC开源RPC框架sofa优势的解读,咱们了解到,RPC 也能够基于 HTTP 实现,如上图 GRPC ,就是 google 基于 HTTP2.0协议,RPC 也能够基于 TCP,Sockets实现。json

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.api

而 HTTP(HyperText Transfer Protocol) 是基于 TCP 的实现的超文本传输协议,HTTP 是无状态协议;最初用于浏览器与服务器的通讯,后来普遍用于各个服务间的通讯。浏览器

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

简单来讲,成熟的rpc库相对http容器,更多的是封装了“服务发现”,"负载均衡",“熔断降级”一类面向服务的高级特性,rpc框架是面向服务的更高级的封装。若是把一个http servlet容器上封装一层服务发现和函数代理调用,那它就已经能够作一个rpc框架了。
因此为何要用rpc调用?由于良好的rpc调用是面向服务的封装,针对服务的可用性和效率等都作了优化。单纯使用http调用则缺乏了这些特性。

这里能够关注知乎了解更多:既然有 HTTP 请求,为何还要用 RPC 调用?

3、Nodejs有哪些比较流行的 RPC 框架?

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

4、实践

基于“Eggjs 和 SOFA 的跨语言互调” 咱们尝试了 Eggjs 下的 rpc 调用。

(一)zookeeper介绍

回到最初看 RPC 的原理, Client 端没有向 Server 端发起通讯,而是须要Client Stub 和 Server Stub的桥接,完成 Client 和 Server 的通讯; 问题来了:

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

Zookeeper 在此实现的框架上的做用就是解决上面两个问题,详情可参考:ketao1989.github.io/2016/12/10/…

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

Zookeeper 在解决服务地址储存的所作事情就如DNS和 nginx同样。 接下来主要从 “node服务 ——> Java 服务“ 和 “ Java 服务 ——> node服务” (使用sofa调用)来尝试

1. 配置

  • 经过 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   
复制代码
  • 进入生成好的项目目录,并安装依赖

  • 安装 egg-sofa-rpc 插件和 egg-rpc-generator 工具
    $ npm i egg-sofa-rpc --save
    $ npm i egg-rpc-generator --save-dev

  • 配置 package.json 的 scripts 节点,增长一个命令 "rpc": "egg-rpc-generator"

{
 "scripts": {
   "dev": "egg-bin dev",
   "rpc": "egg-rpc-generator"
 }
}
复制代码

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

"egg": {
   "framework": "sofa-node"
 }
复制代码
  • 配置 config/plugin.js 开启 egg-sofa-rpc 插件
// config/plugin.js

exports.sofaRpc = {
enable: true,
package: 'egg-sofa-rpc',
};
复制代码
  • config/config.default.js 配置zookeeper
// config/config.default.js
'use strict';

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

2.配置接口

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 文件定义了一个服务:实际上就是须要调用 Java 项目中com.alipay.sofa.rpc.protobuf.ProtoService,它有一个叫 echoObj 的方法,入口参数类型是 EchoRequest,返回值类型是 EchoResponse。

该 Java 项目在此 Eggjs RPC Example

(二)protobuf

1.protobuf 是什么?

Protocol buffers 是一种语言中立,平台无关,可扩展的序列化数据的格式,可用于通讯协议,数据存储等。 Protocol buffers 在序列化数据方面,它是灵活的,高效的。相比于 XML 来讲,Protocol buffers 更加小巧,更加快速,更加简单。一旦定义了要处理的数据的数据结构以后,就能够利用 Protocol buffers 的代码生成工具生成相关的代码。甚至能够在无需从新部署程序的状况下更新数据结构。只需使用 Protobuf 对数据结构进行一次描述,便可利用各类不一样语言或从各类不一样数据流中对你的结构化数据轻松读写。 它很适合作数据存储或 RPC数据交换格式。可用于通信协议、数据存储等领域的语言无关、平台无关、可扩展的序列化结构数据格式。目前提供了 C++、Java、Python 三种语言的 API。

protobuf在数据序列化的应用有如下几方面:

  • 在 config/proxy.js 中配置要调用的服务信息:
'use strict';
    
    module.exports = {
      services: [{
        appName: 'sofarpc',
        api: {
          ProtoService: 'com.alipay.sofa.rpc.protobuf.ProtoService',
        },
      }],
    };
复制代码
  • 在根目录下运行 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"
复制代码

2.Egg-sofa-generator

SOFARPC 插件是为 egg 提供调用和发布 RPC 服务的能力,该插件提供调用其余系统暴露的 SOFARPC 接口的能力。
那么这个插件如何应用在实际项目? 实际上就是生成了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 */
复制代码

如何调用该方法呢?

  • 在路由里经过 ctx.proxy去调用
// 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;
复制代码
npm run dev
复制代码

调试结果方面,调用接口以后以下图所示:

5、小结

伴随 SOFARPC的开源,sofa-bolt-node 和 sofa-rpc-node 两个 Nodejs RPC 基础模块也逐步完善。本文主要将Eggjs 和 SOFA(Java)连通,尝试去提供EGG.js中RPC的一些引用。node社区不断完善,将来node.js在RPC中的尝试也能够愈来愈多。

6、参考文献