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、參考文獻

相關文章
相關標籤/搜索