遠程通訊協議:從 CORBA 到 gRPC

自從產業界發明機器聯網的那一天就已經開始探索最優的遠程通訊機制。操做系統如 UNIX、Windows 和 Linux 等都有實現遠程通訊的內部協議,挑戰在於如何向開發人員開放一個通訊框架。node

1、遠程調用技術簡史

在20世紀90年代,當 TCP/IP 協議日臻成熟變成網絡通訊的黃金標準時,焦點轉移到跨平臺通訊 —— 一臺計算機能夠經過某種類型網絡在另外一臺計算機上發起一個動做。例如如 CORBA、DCOM、Java RMI 技術,在覈心網絡基礎設施之上創造了一個對開發者友好的抽象層。這些技術還試圖發展出一套與開發語言無關的通訊框架,這一點對於客戶機/服務器體系結構相當重要。python

隨着本世紀初 Web 技術的演進,HTTP 逐漸演變爲事實上的通訊標準。HTTP 結合 XML 提供了一種自我描述、不依賴語言、與平臺無關的遠程通訊框架。這種結合的成果是 SOAP 和 WSDL 標準,它們保證了在各類運行環境和平臺之間實現互操做的標準化。git

下一個衝擊互聯網的浪潮是 Web 編程。許多開發人員發現定義 SOAP 標準的 HTTP 和 XML 的組合過於嚴格。這時 JavaScript 和 JSON 開始流行了。Web 2.0 現象(API 發揮了關鍵做用), JSON 替代 XML 成爲首選的協議。HTTP 和 JSON 這對致命的組合,催生了一個新的非官方標準 REST 。SOAP 要求嚴格遵照標準和結構定義,僅侷限於大型企業應用程序,而 REST 在當代開發人員中很受歡迎。github

1.1 HTTP, REST 和微服務

歸功於 JavaScript 框架,Node.js 以及文檔數據庫的發展,REST 在 Web 開發者中廣受歡迎。許多應用程序開始基於 REST 實現 ,即便是內部序列化和通訊模式領域。但 HTTP 是最有效的消息交換協議嗎?即便在同一上下文、同一網絡,或者是同一臺機器上運行的服務之間?HTTP 的便捷性與高性能之間須要做出權衡,這促使咱們回到問題的起點,尋找微服務架構中最優的通訊框架。數據庫

進入 gRPC 時代 —— 來自谷歌,現代的輕量級通訊協議。這是一個高性能的、開源的通用遠程過程調用(RPC) 框架,它能夠在多種開發語言、任何操做系統上運行。npm

gRPC 在推出的第一年內就被 CoreOS,Netflix,Square 和 Cockroach Labs 等機構採用。 CoreOS 團隊的 Etcd,是一種分佈式鍵/值存儲服務,採用 gRPC 實現端通訊。電信公司如 Cisco,Juniper 和 Arista 都使用 gRPC 實現數據流遙測和網絡設備配置。編程

1.2 什麼是 gRPC ?

當我第一次遇到 gRPC,它使我想到 CORBA。兩個框架都基於語言無關的接口定義語言(IDL) 聲明服務,經過特定的語言綁定實現。 安全

CORBA 和 gRPC 兩者的設計,都是爲了使客戶端相信服務器在同一臺機器。客戶機在樁(Stub)上調用一個方法(method),調用過程由底層協議透明地處理。bash

gRPC 的祕訣在於處理序列化的方式。gRPC 基於 Protocol Buffer,一個開源的用於結構化數據序列化機制,它是語言和平臺無關的。Protocol Buffer 的描述很是詳細,與 XML 相似。可是它們比其餘的協議格式更小,更快,效率更高。任何須要序列化的自定義數據類型在 gRPC 被定義爲一個 Protocol Buffer 。服務器

Protocol Buffer 的最新版本是 proto3,支持多種開發語言的代碼生成,Java , C++,Python,Ruby , Java Lite , JavaScript,Objective-C 和 C # 。當一個 Protocol Buffer 編譯爲一個特定的語言,它的訪問器(setter 和 getter)爲每一個字段提供定義。

相比於 REST + JSON 組合 ,gRPC 提供更好的性能和安全性。它極大的促進了在客戶端和服務器之間使用 SSL / TLS 進行身份驗證和數據交換加密。

爲何微服務開發者須要使用 gRPC ?gRPC 採用 HTTP / 2 以支持高性能的、可擴展的 API 。報文使用二進制而不是文本通訊能夠保持載荷緊湊、高效。HTTP / 2 請求在一個 TCP 鏈接上可支持多路複用,容許多個消息併發傳送而不影響網絡資源利用率。gRPC 使用報頭壓縮(header compression )來減小請求和響應的大小。

2、gRPC 簡介

2.1 建立 gRPC 服務的流程

  1. 在 Protocol Buffer (.proto) 文件中描述服務和載荷結構
  2. 從 .proto 文件生成 gRPC 代碼
  3. 用一種開發語言實現服務端
  4. 建立一個客戶端調用服務
  5. 運行服務端和客戶端

Note:Node.js 客戶端不須要生成存根(Stub),只要 Protocol Buffer 文件是可訪問的,它就能夠直接與服務端對話。

3、gRPC 示例代碼

爲了進一步熟悉 gRPC,咱們將用 Python 語言建立一個簡單的計算服務。它將同時被一個 Python 客戶端和一個 Node.js 客戶端調用。如下測試示例運行在 Mac OS X 。

你能夠從 GitHub 庫 https://github.com/grpc/grpc/tree/master/examples 訪問源代碼,在本身的機器上運行示例。

  • 環境準備
// 配置 Python gRPC
python -m pip install virtualenv
virtualenv venv
source venv/bin/activate
python -m pip install --upgrade pip

//安裝 gRPC 和 gRPC Tools
python -m pip install grpcio
python -m pip install grpcio-tools

// 配置 Node.js gRPC
npm install grpc --global

//建立目錄
mkdir Proto
mkdir Server
mkdir -p Client/Python
mkdir -p Client/Node
  • 建立 Protocol Buffer 文件
//Proto/Calc.proto
syntax = "proto3";

package calc;

service Calculator {
  rpc Add (AddRequest) returns (AddReply) {}
  rpc Substract (SubstractRequest) returns (SubstractReply) {}
  rpc Multiply (MultiplyRequest) returns (MultiplyReply) {}
  rpc Divide (DivideRequest) returns (DivideReply) {}
}

message AddRequest{
  int32 n1=1;
  int32 n2=2;
}
message AddReply{
  int32 n1=1;
}
message SubstractRequest{
  int32 n1=1;
  int32 n2=2;
}
message SubstractReply{
  int32 n1=1;
}
message MultiplyRequest{
  int32 n1=1;
  int32 n2=2;
}
message MultiplyReply{
  int32 n1=1;
}
message DivideRequest{
  int32 n1=1;
  int32 n2=2;
}
message DivideReply{
  float f1=1;
}
  • 生成 Python 服務端和客戶端代碼
$ python -m grpc.tools.protoc  --python_out=. --grpc_python_out=. --proto_path=. Calc.proto
$ cp Calc_pb2.py ../Server
$ cp Calc_pb2.py ../Client/Python
$ cp Calc.proto ../Client/Node
  • 建立服務端
# Server/Calc_Server.py
from concurrent import futures
import time

import grpc

import Calc_pb2
import Calc_pb2_grpc

_ONE_DAY_IN_SECONDS = 60 * 60 * 24

class Calculator(Calc_pb2.CalculatorServicer):

 def Add(self, request, context):
   return Calc_pb2.AddReply(n1=request.n1+request.n2)

 def Substract(self, request, context):
   return Calc_pb2.SubstractReply(n1=request.n1-request.n2)

 def Multiply(self, request, context):
   return Calc_pb2.MultiplyReply(n1=request.n1*request.n2)

 def Divide(self, request, context):
   return Calc_pb2.DivideReply(f1=request.n1/request.n2)

def serve():
 server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
 Calc_pb2_grpc.add_CalculatorServicer_to_server(Calculator(), server)
 server.add_insecure_port('[::]:50050')
 server.start()

 try:
   while True:
     time.sleep(_ONE_DAY_IN_SECONDS)
 except KeyboardInterrupt:
   server.stop(0)

if __name__ == '__main__':
 serve()
  • 啓動服務端
python Calc_Server.py
  • 建立 Python 客戶端
# Client/Python/Calc_Client.py

from __future__ import print_function

import grpc
import Calc_pb2
import Calc_pb2_grpc

def run():
 channel = grpc.insecure_channel('localhost:50050')
 stub = Calc_pb2_grpc.CalculatorStub(channel)

 response = stub.Add(Calc_pb2.AddRequest(n1=20,n2=10))
 print(response.n1)
 response = stub.Substract(Calc_pb2.SubstractRequest(n1=20,n2=10))
 print(response.n1)
 response = stub.Multiply(Calc_pb2.MultiplyRequest(n1=20,n2=10))
 print(response.n1)
 response = stub.Divide(Calc_pb2.DivideRequest(n1=20,n2=10))
 print(response.f1)

if __name__ == '__main__':
  run()
  • 建立 Node.js 客戶端
//Client/Node/Calc_Client.js
var PROTO_PATH = 'Calc.proto';

var grpc = require('grpc');
var calc_proto = grpc.load(PROTO_PATH).calc;
var params={n1:20, n2:10};

function main() {
 var client = new calc_proto.Calculator('localhost:50050',
                                      grpc.credentials.createInsecure());

 client.divide(params, function(err, response) {
   console.log(response.f1);
 });

 client.multiply(params, function(err, response) {
   console.log(response.n1);
 });

 client.substract(params, function(err, response) {
   console.log(response.n1);
 });

 client.add(params, function(err, response) {
   console.log(response.n1);
 });

}

main();
  • 啓動客戶端 Node.js/Python
$ python Calc_Client.py
30
10
200
2.0

$ node Calc_Client.js
30
10
200
2.0

擴展閱讀:開發語言&代碼工程

更多精彩內容掃碼關注公衆號:RiboseYim's Blog:https://riboseyim.github.io/2017/10/30/Protocol-gRPC/ 微信公衆號

相關文章
相關標籤/搜索