1. 程式人生 > >高效傳輸資料格式以及基於HTTP2的RPC框架---gRPC的使用

高效傳輸資料格式以及基於HTTP2的RPC框架---gRPC的使用

ProtoBuffer的介紹

google有一款非常高效的資料傳輸格式框架ProtoBuffer。在java中使用protobuffer作為序列化效率比jdk自身的serializable介面效率高的多(github上有個對於序列號效能的研究https://github.com/eishay/jvm-serializers/wiki),這在快取的時候效率非常高。當然,如此優秀的資料格式框架並不是僅僅使用在快取上的,既然壓縮(姑且將其簡單理解為壓縮演算法吧)如此高效,那麼使用在網路IO傳輸中比JSON或許XML而言效率也為提升很多吧。

gRPC的介紹

gRPC是google開發的一款RPC框架,RPC Server與RPC Clinet之間的資料傳輸就是剛剛提到的ProtoBuffer,並且該RPC框架還是基於HTTP2的。因此,HTTP2的多路複用,基於流的傳輸在gRPC上也有相應的實現。

ProtoBuffer格式的定義

syntax = "proto3";

package vsig;

service VSIGProto {
  rpc setAcl (ACLRequest) returns (Reply) {}
  rpc openNtp  (NTPConfig) returns (Reply) {}
}
message ACLRequest {
  string extranetIp = 1;
  int32  devType = 2;
  string intranetIp = 3;
}
message InterfaceInfoRequest {
  string
name =1; string ip = 2; string mask=3; string gateway=4; } message NTPInfoRequest{ bool state = 1; string ntpServerIp =2; int32 ntpServerPort =3; } message NTPConfig{ NTPInfoRequest ntpInfo = 1; InterfaceInfoRequest br0Info = 2; } message Reply { string message = 1; }

.proto檔案中主要定義了三部分東西:

  • RPC的方法名以及接受的引數和返回的引數
  • RPC方法接受引數的格式
  • RPC方法返回的格式

一個方法僅能接受一個引數,因為筆者定義NTPConfig裡面又包含了兩個物件,這樣保證了openNtp方法僅接收了一個物件

對於定義的message,每個值都有一個唯一的number型別的數字,根據官方文件的解釋:它是用於以訊息二進位制格式標識欄位,並且在使用過程中不能隨便更改,否則會導致資料無法還原。同時,如果數字定義為1~15則使用一個位元組來儲存,而16~2047需要使用兩個位元組來儲存。

gRPC Server的實現

定義好.proto之後就可以使用該檔案來使用grpc客戶端與伺服器端了,gRPC的客戶端與伺服器端必須使用同一個.proto檔案

gRPC支援眾多常見的程式語言,筆者使用java與node兩種語言實現gRPC。

NodeJS gRPC Server實現

package.json:

{
  "name": "grpc-examples",
  "version": "0.1.0",
  "dependencies": {
    "async": "^1.5.2",
    "google-protobuf": "^3.0.0",
    "grpc": "^1.0.0",
    "lodash": "^4.6.1",
    "minimist": "^1.2.0"
  }
}

gRPC伺服器的編碼實現:

//上述定義的.proto的路徑
const PROTO_PATH = '../../vsig.proto';

const grpc = require( 'grpc' );
//最後vsig是.proto中的package
const proto = grpc.load( PROTO_PATH ).vsig;
//定義rpc Server的ip與埠
const rpcHost = '127.0.0.1';
const rpcPort = 50051;

//定義方法的對映,因為方法最終是在該類中實現的,因此定義改類與.proto中的方法的對映。左邊為.proto中的方法名,右邊為實現
const methodCover = {
  setAcl: setAcl,
  openNtp: openNTP
};

function setAcl( call, callback ) {
  //call.request即為該方法在.proto中定義的引數接收的message物件
  console.log( call.request);
  //該回調即為對客戶端的方法,引數1是error,引數二與.proto中方法的返回值對應
  callback( null, {
    message: "rpc call setAcl method success"
  } )
}

function openNTP( call, callback ) {
  const ntpInfo = call.request;
  console.log(ntpInfo);
  callback(null,{
    message:"rpc call openNTP call success"
  })
}
function main() {
  var server = new grpc.Server();
  //VSIGProto即為.proto中的server的名稱,引數二為方法對映
  server.addProtoService( proto.VSIGProto.service, methodCover );
  const grpcIn = grpc.ServerCredentials.createInsecure();
  //繫結埠
  server.bind( rpcHost + ":" + rpcPort, grpcIn );
  //啟動
  server.start();
}

main();

Java gRPC Server的實現

gRPC Client的實現

如上定義好服務端之後,將監聽指定的埠,客戶端只需要對改埠傳送請求即可。

Node gRPC Client的實現

const PROTO_PATH = '../vsig.proto';

const grpc = require( 'grpc' );
//最後vsig是.proto中的package
const proto = grpc.load( PROTO_PATH ).vsig;
const rpcHost = '127.0.0.1';
const rpcPort = 50051;
//VSIGProto是.proto中service中的VSIGProto
const client = new proto.VSIGProto( rpcHost + ":" + rpcPort, grpc.credentials.createInsecure() );

class RpcClient {
  setAcl( acl, cb ) {
  //執行rpc呼叫
    client.setAcl( acl, function( err, response ) {
      cb( err, response )
    } );
  }
  //封裝RPC的方法
  openNTP( ntpconfig, cb ) {
  //執行rpc呼叫
    client.openNtp( ntpconfig, function( err, response ) {
      cb( err, response );
    } );
  }
}
const rpcClient = new RpcClient();
module.exports = rpcClient;

Java gRPC Client的實現