此前,我在做跨語言呼叫時,用的是 Facebook 的 Thrift,挺輕量的,還不錯。
Thrift是一種介面描述語言和二進位制通訊協定,它被用來定義和建立跨語言的服務。它被當作一個遠端過程呼叫(RPC)框架來使用,是由Facebook為「大規模跨語言服務開發」而開發的。它通過一個程式碼生成引擎聯合了一個軟體棧,來建立不同程度的、無縫的跨平臺高效服務,可以使用C#、C++(基於POSIX相容系統)Cappuccino、Cocoa、Delphi、Erlang、Go、Haskell、Java、Node.js、OCaml、Perl、PHP、Python、Ruby和Smalltalk程式語言開發。 2007由Facebook開源,2008年5月進入Apache孵化器, 2010年10月成為Apache的頂級專案。
最近的專案中也有類似的需求,這次打算使用 Google 的 gRPC,原因是 gRPC 知名度也很高,之前一直想用但沒有場景,加上 AspNetCore 的檔案裡有介紹,看起來官方也是推薦使用這種方式進行 RPC 呼叫。
所以就果斷開始了 gRPC 實踐,最終做出來的效果還是可以的(這是後話)。
本文主要介紹 C# 與 Python 之間,使用 gRPC 進行跨語言呼叫。
遠端過程呼叫(Remote Procedure Call,縮寫為 RPC)是一個計算機通訊協定。該協定允許執行於一臺計算機的程式呼叫另一臺計算機的子程式,而程式設計師無需額外地為這個互動作用程式設計。 比如 Java RMI(遠端方法呼叫(Remote
Method Invocation)。能夠讓在某個java虛擬機器器上的物件像呼叫本地物件一樣呼叫另一個java虛擬機器器中的物件上的方法)。
從上圖可以看出, RPC 本身是 client-server模型,也是一種 request-response 協定。
有些實現擴充套件了遠端呼叫的模型,實現了雙向的服務呼叫,但是不管怎樣,呼叫過程還是由一個使用者端發起,伺服器端提供響應,基本模型沒有變化。
服務的呼叫過程為:
- client呼叫client stub,這是一次本地過程呼叫
- client stub將引數打包成一個訊息,然後傳送這個訊息。打包過程也叫做 marshalling
- client所在的系統將訊息傳送給server
- server的的系統將收到的包傳給server stub
- server stub解包得到引數。 解包也被稱作 unmarshalling
- 最後server stub呼叫服務過程. 返回結果按照相反的步驟傳給client
gRPC 是一種與語言無關的高效能遠端過程呼叫 (RPC) 框架。
前文有說過,這個是 Google 開發的,以下是 AspNetCore 的檔案中,對 gRPC 的介紹。
gRPC 的主要優點是:
- 現代高效能輕量級 RPC 框架。
- 協定優先 API 開發,預設使用協定緩衝區,允許與語言無關的實現。
- 可用於多種語言的工具,以生成強型別伺服器和使用者端。
- 支援使用者端、伺服器和雙向流式處理呼叫。
- 使用 Protobuf 二進位制序列化減少對網路的使用。
這些優點使 gRPC 適用於:
- 效率至關重要的輕量級微服務。
- 需要多種語言用於開發的 Polyglot 系統。
- 需要處理流式處理請求或響應的對等實時服務。
gRPC 使用 .proto
檔案來定義介面和資料型別,同時提供了通過 .proto
檔案生成不同語言呼叫程式碼的工具。
本文的專案是使用 C# 呼叫 Python 寫的服務,這個服務是一個大語言模型。
在本專案中,C# 專案作為使用者端,Python 專案作為伺服器端。
.proto
檔案第一步,要先編寫用於定義介面和資料結構的 .proto
檔案。
將下面程式碼儲存到 chat.proto
檔案裡,接下來在要接入 gRPC 的每個專案裡,都要複製一份這個檔案。
syntax = "proto3";
import "google/protobuf/wrappers.proto";
option csharp_namespace = "AIHub.Blazor";
package aihub;
service ChatHub {
rpc Chat (ChatRequest) returns (ChatReply);
rpc StreamingChat (ChatRequest) returns (stream ChatReply);
}
message ChatRequest {
string prompt = 1;
repeated google.protobuf.StringValue history = 2;
int32 max_length = 3;
float top_p = 4;
float temperature = 5;
}
message ChatReply {
string response = 1;
}
這裡定義了兩個 Chat 方法,其中一個是一元呼叫,一個是流式輸出。
gRPC 服務可以有不同型別的方法,服務傳送和接收訊息的方式取決於所定義的方法的型別
都挺好理解的,在 proto
定義裡面也很易懂,stream
放在哪裡,哪裡就是流式。
stream
放在請求引數,那使用者端輸入就是流式的,放在返回值前面,那伺服器端的返回就是流式。都放就代表雙向流式。
接著又定義了用到的資料結構,一個輸入引數 ChatRequest
,一個返回引數 ChatReply
。
string prompt = 1;
這裡賦值的意思是這個引數的位置,不是定義變數的賦值