使用命名管道承載gRPC

波多爾斯基發表於2020-07-11

最近GRPC很火,感覺整RPC不用GRPC都快跟不上時髦了。

gRPC設計

gRPC是一種與語言無關的高效能遠端過程呼叫 (RPC) 框架。剛好需要使用一個的RPC應用系統,自然而然就盯上了它,但是它真能夠解決所有問題嗎?不見得,先看看他的優點:

gRPC的主要優點:

  • 現代高效能輕量級 RPC 框架。
  • 協定優先 API 開發,預設使用協議緩衝區,允許與語言無關的實現。
  • 可用於多種語言的工具,以生成強型別伺服器和客戶端。
  • 支援客戶端、伺服器和雙向流式處理呼叫。
  • 使用 Protobuf 二進位制序列化減少對網路的使用。

對應的適用場景:

  • 微服務:gRPC 設計用於低延遲和高吞吐量通訊。 gRPC 對於效率至關重要的輕量級微服務非常有用。
  • 點對點實時通訊:gRPC 對雙向流式傳輸提供出色的支援。 gRPC 服務可以實時推送訊息而無需輪詢。
  • 多語言環境:gRPC 工具支援所有常用的開發語言,因此,gRPC 是多語言環境的理想選擇。
  • 網路受限環境:gRPC 訊息使用 Protobuf(一種輕量級訊息格式)進行序列化。 gRPC 訊息始終小於等效的 JSON 訊息。

gRPC還是有缺點的:

  • 瀏覽器支援受限:絕大數瀏覽器不支援HTTP/2
  • 非人工可讀取:proto檔案規定的格式在通訊中會序列化成二進位制資料,人工解析較為困難。

不適用的場景與替代:

  • 瀏覽器可訪問的API:gRPC 在瀏覽器中未受到完全支援。 gRPC-Web 可以提供瀏覽器支援,但它具有侷限性並引入了伺服器代理。
  • 廣播實時通訊:gRPC 支援通過流式傳輸進行實時通訊,但不存在將訊息廣播到註冊連線的概念。 例如,在聊天室方案中,應將新的聊天訊息傳送到聊天室中的所有客戶端,這要求每個 gRPC 呼叫將新的聊天訊息單獨流式傳輸到客戶端。 SignalR 是適用於此方案的框架。 SignalR 具有永續性連線的概念,並內建對廣播訊息的支援。
  • 程式間通訊:程式必須託管 HTTP/2 伺服器才能接受傳入的 gRPC 呼叫。 對於 Windows,程式間通訊管道是一種快速、輕便的通訊方法。

目標分析

我需要有一個能夠實現遠端呼叫的好辦法,系統支援Windows就好,最好效能高一些(資料量大),程式小一點,但是我也不想直接處理二進位制資料流(最好能有封裝的框架)。

考慮程式通訊常用的:

  • 訊號/訊號量:簡單,能夠承載的訊息內容較少。
  • 訊息佇列:支援訊息,功能較為強大。
  • 共享記憶體:效能最強,但只限於單機。
  • 管道:效能較強,但是隻支援stream。
  • Socket:最靈活,但是需要有網路卡。

首先排除訊號/訊號量,處理的資訊量太小了;然後共享記憶體也排除,只能單機不符合我的要求;剩下的三個似乎都可以滿足要求,可以在這個基礎上建立RPC,而gRPC就是建立在socket(HTTP/2)上的,就像上面講的,要自己整合一個HTTP/2伺服器(比如Kestrel)才行,不夠輕量化;剩下的兩個Windows都有內建支援,可以考慮一下。

本著拿來主義的思想,我在github上找到一個grpc-dotnet-namedpipes,支援在命名管道上實現gRPC,相當於在stream上封裝了一層,不用直接處理二進位制資料流了。

用作者自己的話來說,這麼做相較於普通的gRPC有幾個優點:

  • 更優秀的訪問控制;
  • 純.NET庫,不需要帶ASP.NET Core或者超過3MB的非託管庫依賴;
  • 啟動時間更快
  • 2-3倍的資料吞吐量
  • 沒有防火牆警告
  • 不需要網路卡

實現

建立一個proto

  1. 建立一個.NET Library
  2. 新增一個proto檔案,可以直接使用微軟的簡單例子
syntax = "proto3";

service Greeter {
  rpc SayHello (HelloRequest) returns (HelloReply);
}

message HelloRequest {
  string name = 1;
}

message HelloReply {
  string message = 1;
}
  1. 新增nuget包:Google.Protobuf,Grpc.Core,Grpc.Tools
  2. 單擊proto檔案,在屬性對話方塊,選擇生成操作為Protobuf Compiler

建立Client

新建一個Console程式,新增上面的專案引用,輸入以下程式碼:

var server = new NamedPipeServer("MY_PIPE_NAME");
Greeter.BindService(server.ServiceBinder, new GreeterService());
server.Start();

新增GrpcDotNetNamedPipes的nuget依賴:

Install-Package GrpcDotNetNamedPipes

建立Server

再新建一個Console程式,新增上面的專案引用,也新增那個nuget依賴和一些別的依賴,輸入以下程式碼:

var channel = new NamedPipeChannel(".", "MY_PIPE_NAME");
var client = new Greeter.GreeterClient(channel);

var response = await client.SayHelloAsync(
	new HelloRequest { Name = "World" });

Console.WriteLine(response.Message);

然後執行就能看見熟悉的Hello World了,用起來和gRPC的標準實現沒太大區別。

總結

完整程式碼見gRPC_Demo

這種方式也有它的侷限性,首先是Windows的命名管道與Linux上面的實現是不同的,所以並不能實現直接跨平臺通訊;然後就是這個對於其他語言的開發的gRPC也不是完全相容的,需要其他語言開發的程式也做命名管道的適配才行,換言之,它不是通用標準。所以,對於一般的gRPC應用,還是更推薦使用標準實現。

參考

相關文章