1. 程式人生 > >Node.js與RPC 的實踐方案-Eggjs使用sofa-rpc-node模組

Node.js與RPC 的實踐方案-Eggjs使用sofa-rpc-node模組

一、前言

SOFARPC 是螞蟻金服開源的一個高可擴充套件性、高效能、生產級的 Java RPC 框架,提供了豐富的模型抽象和可擴充套件介面,包括過濾器、路由、負載均衡等等,致力於簡化應用之間的 RPC 呼叫,為應用提供方便透明、穩定高效的點對點遠端服務呼叫方案。

egg.js作為一個成熟的開源專案,清晰的定義了從配置、路由、擴充套件、中介軟體到控制器、定時任務等各個 Web 應用研發過程中一些最基礎的概念,這樣不同團隊的開發者使用框架寫出來的程式碼風格會更一致,接手老專案的上手成本也會更低。

本文將簡單介紹egg.js和sofa(Java)的相互作用,利用sofarpc的開源,展現一個初步的node.js RPC 解決方案。

二、RPC介紹

RPC (Remote Procedure Call) 即 遠端過程呼叫,就是像呼叫本地的函式一樣去呼叫遠端的函式。簡單講,就是本地呼叫的邏輯處理的過程放在的遠端的機器上,而不是本地服務代理來處理。

其實 HTTP 也可以實現遠端呼叫的效果,那麼 HTTP 與 RPC 到底是什麼關係呢?有什麼區別呢?

經過了解,我們發現RPC 和 HTTP 其實是不在同一個層級的概念。

(一)RPC與HTTP區別

RPC 通常所講是一個框架

結合上圖,阿里對RPC開源RPC框架sofa優點的解讀,我們瞭解到,RPC 也可以基於 HTTP 實現,如上圖 GRPC ,就是 google 基於 HTTP2.0協議,RPC 也可以基於 TCP,Sockets實現。

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.

而 HTTP(HyperText Transfer Protocol) 是基於 TCP 的實現的超文字傳輸協議,HTTP 是無狀態協議;最初用於瀏覽器與伺服器的通訊,後來廣泛用於各個服務間的通訊。

RPC遠端過程呼叫RPC框架可以的通訊過程可以使用各種通訊協議(如 HTTP,TCP以及各種自定義協議)實現。

簡單來說,成熟的rpc庫相對http容器,更多的是封裝了“服務發現”,"負載均衡",“熔斷降級”一類面向服務的高階特性,rpc框架是面向服務的更高階的封裝。如果把一個http servlet容器上封裝一層服務發現和函式代理呼叫,那它就已經可以做一個rpc框架了。
所以為什麼要用rpc呼叫?因為良好的rpc呼叫是面向服務的封裝,針對服務的可用性和效率等都做了優化。單純使用http呼叫則缺少了這些特性。

這裡可以關注知乎瞭解更多:既然有 HTTP 請求,為什麼還要用 RPC 呼叫?

三、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介紹

回到最初看 RPC 的原理, Client 端沒有向 Server 端發起通訊,而是需要Client Stub 和 Server Stub的橋接,完成 Client 和 Server 的通訊; 問題來了:

  1. Client Stub 和 Server Stub的橋接過程中如何找到對應的 Server?(服務地址儲存
  2. Server 接收到請求後,Client如何接收處理結果?(服務狀態感知

Zookeeper 在此實現的框架上的作用就是解決上面兩個問題,詳情可參考:https://ketao1989.github.io/2016/12/10/rpc-theory-in-action/

對比起 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
    > [email protected] 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

除錯結果方面,呼叫介面之後如下圖所示:

五、小結

伴隨 SOFARPC的開源,sofa-bolt-node 和 sofa-rpc-node 兩個 Nodejs RPC 基礎模組也逐步完善。本文主要將Eggjs 和 SOFA(Java)連通,嘗試去提供EGG.js中RPC的一些引用。node社群不斷完善,未來node.js在RPC中的嘗試也可以越來越多。

六、參考文獻