遠程通信協議:從 CORBA 到 gRPC

摘要: - 一、遠程調用技術簡史 - 二、gRPC 簡介 - 三、gRPC 示例代碼

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

一、遠程調用技術簡史

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

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

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

1.1 HTTP, REST 和微服務

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

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

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

1.2 什麼是 gRPC ?

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

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

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 )來減少請求和響應的大小。

二、gRPC 簡介

2.1 創建 gRPC 服務的流程

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

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

三、gRPC 示例代碼

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

你可以從 GitHub 庫 github.com/grpc/grpc/tr 訪問源代碼,在自己的機器上運行示例。

  • 環境準備

// 配置 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

更多精彩內容掃碼關注公眾號:RiboseYims Blog:https://riboseyim.github.io/2017/10/30/Protocol-gRPC/ n

weixin.qq.com/r/q3VkfFf (二維碼自動識別)


推薦閱讀:

認識微服務——一顆銀彈
「微」力無限,微服務架構應用實踐
服務註冊發現與調度
沃爾瑪的數字化平台分析

TAG:gRPC | 软件架构 | 微服务架构 |