dotnet core gRPC 原文在本人公眾號中,歡迎關註我,時不時的會分享一些心得 HTTP和RPC是現代微服務架構中很常用的數據傳輸方式,兩者有很多相似之處,但是又有很大的不同。HTTP是一種規範性、通用性、非常標準的傳輸協議,幾乎所有的語言都支持,如果要確保各平臺無縫銜接,可以考慮使用HT ...
dotnet core gRPC
原文在本人公眾號中,歡迎關註我,時不時的會分享一些心得
HTTP和RPC是現代微服務架構中很常用的數據傳輸方式,兩者有很多相似之處,但是又有很大的不同。HTTP是一種規範性、通用性、非常標準的傳輸協議,幾乎所有的語言都支持,如果要確保各平臺無縫銜接,可以考慮使用HTTP協議,例如現在常規的RestFUL,整個傳輸過程通常使用Json數據格式。以至於不管是前端還是後端都可以很好的對接。
RPC協議不僅僅是服務間通信協議,甚至是進程間也存在。可以降低諸多微服務之間調用成本,屏蔽了通訊細節,調用遠程方法處理數據時像調用本地方法一樣絲滑順暢。但是對前端和瀏覽器不是特別友好。
GRPC是google制定實現的一種Rpc形式,官網解釋是:
gRPC是可以在任何環境中運行的現代開源高性能RPC框架。它可以通過可插拔的支持來有效地連接數據中心內和跨數據中心的服務,以實現負載平衡,跟蹤,運行狀況檢查和身份驗證。它也適用於分散式計算的最後一英里,以將設備,移動應用程式和瀏覽器連接到後端服務。
gRPC主要包括四個特點:
- 簡單的服務定義且不局限於語言:gRPC基於ProtoBuf協議構建,該協議提供了強大的系列化工具和語言定義服務。
- 跨語言和平臺:可自動為多語言或平臺生成客戶端和服務端內容。
- 快速啟動和擴展性:只需極少代碼就可以生成整個通訊環境,可以擴展數百萬rpc請求。
雙向流和集成身份驗證:基於HTTP/2的傳輸機制以及雙向流和而完全可集成的插入式身份驗證機制。
ProtoBuf協議:
該協議是一種序列化結構化數據的靈活,高效,自動化的機制,比xml更小,更快,更簡單。您定義要一次構造數據的方式,然後可以使用生成的特殊源代碼輕鬆地使用各種語言在各種數據流中寫入和讀取結構化數據。您甚至可以更新數據結構,而不會破壞已針對“舊”格式編譯的已部署程式。
協議文件定義方式:
這是一個簡單的定義,此文件在.proto文件中定義,與語言無關。每個消息類型都有一個或多個唯一編號的欄位。每個欄位都有一個名稱和一個值類型,其中值類型可以是數字(整數或浮點數),布爾值,字元串等。定義好後可以使用編譯器生成對應語言的操作對象。
更多協議內容請參閱:https://developers.google.com/protocol-buffers/docs/overview。.net Core3.0 中的Grpc
- 使用Visual Stduio 2019 創建一個gRPC服務
請註意:此處創建的是使用ASP.NET Core的Grpc服務。也就是說,有WebHost去托管服務的。
後面,我們使用Host通用主機進行托管。 - 先來看看Startup類
圖中這兩行代碼是關鍵,services.AddGrpc();將rpc註入到應用程式服務中,
endpoints.MapGrpcService();則是在中間件啟用監聽rpc請求。
預設情況下此時生成項目後,proto文件只會生成Serve的代碼。客戶端代碼生成需要單獨做配置。
微軟文檔中是將proto文件拷貝到client中,去生成client代碼。此時服務和客戶端生成配置就不一樣了:
<ItemGroup>
<Protobuf Include="Protos\greet.proto" GrpcServices="Client" />
</ItemGroup>
我本人習慣將生成的server和client放在一起,打包後供client端和server端使用,只需要這樣設置:
<ItemGroup>
<Protobuf Include="Protos\greet.proto" GrpcServices="Server;Client" />
</ItemGroup>
配置好後重新生成項目,grpctool會自動生成服務端,客戶端代碼。
client調用方式(asp.net core gRPC):
var channel = GrpcChannel.ForAddress("https://localhost:5001");
var client = new Greeter.GreeterClient(channel);
var response = await client.SayHello(
new HelloRequest { Name = "World" });
Console.WriteLine(response.Message);
接下來,我們創建一個簡單的示例
- 創建兩個個Core控制台項目並添加文件夾Protos和Services,分別新增一個demo.proto文件和DemoService.cs文件
- 引入nuget包:
<PackageReference Include="Google.Protobuf" Version="3.10.1" />
<PackageReference Include="Grpc" Version="2.24.0" />
<PackageReference Include="Grpc.Tools" Version="2.24.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="3.0.0" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="3.0.0" />
- 編輯csproj文件:使得支持同時生成client和server代碼(作為demo,server端 應該只作為server。生成client和server的代碼的應該單獨抽出來)
- 服務端:重寫GrpcExampleService.GrpcExampleServiceBase中的rpc方法(也就是.proto文件生成的代碼內容中的rpc服務方法)
public class DemoService: GrpcExampleService.GrpcExampleServiceBase
{
public override Task<AskResponse> Ask(AskRequest request, ServerCallContext context)
{
return Task.FromResult(new AskResponse {Content = "Hello from Ask"});
}
public override Task<ResponseModel> GetName(RequestModel request, ServerCallContext context)
{
return Task.FromResult(new ResponseModel { Name = "Hello Pluto" });
}
}
- 接下來就是創建啟動項(HOST)了(GrpcServer):
這次使用通用主機,不適用asp.net core webhost。
public class GrpcServer:IHostedService
{
private readonly GrpcExampleService.GrpcExampleServiceBase _sampleServiceBase;
public GrpcServer(GrpcExampleService.GrpcExampleServiceBase sampleServiceBase)
{
_sampleServiceBase = sampleServiceBase;
}
/// <summary>
/// 啟動自己定義的rpc服務
/// </summary>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public Task StartAsync(CancellationToken cancellationToken)
{
return Task.Factory.StartNew(() =>
{
GrpcServiceManager.Start(GrpcExampleService.BindService(_sampleServiceBase));
}, cancellationToken);
}
/// <summary>
/// 停止
/// </summary>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public Task StopAsync(CancellationToken cancellationToken)
{
return Task.Factory.StartNew(() =>
{
GrpcServiceManager.Stop();
}, cancellationToken);
}
}
GrpcServiceManager中就是真正的啟動和停止grpc服務:
public class GrpcServiceManager
{
static Server server;
public static void Start(ServerServiceDefinition bindService)
{
if (bindService == null)
throw new ArgumentNullException("bindService");
var services = new List<ServerServiceDefinition>() { bindService };
Start(services); //todo 添加配置,攔截器等等
}
private static void Start(List<ServerServiceDefinition> services)
{
try
{
server = new Server()
{
Ports = { new ServerPort("0.0.0.0", 8890, ServerCredentials.Insecure) }
};
foreach (var service in services)
{
server.Services.Add(service);
}
server.Start();
//todo consul 註冊
}
catch (Exception e)
{
Console.WriteLine(e);
throw;
}
}
public static void Stop()
{
try
{
server?.ShutdownAsync().Wait();
}
catch (Exception e)
{
Console.WriteLine(e);
throw;
}
}
}
註意:這裡應該將proto協議 服務端客戶端端進行分離,協議生成的代碼打包後 被客戶端項目和服務端項目引用,這裡只是為了簡便客戶端直接會引用服務,因為要用到GrpcExampleService.GrpcExampleServiceClient
上邊的todo 後再後續增加對應的功能。
這樣服務端就創建完了。接下來就是創建客戶端進行調用:
控制台客戶端:
main中實現調用邏輯
static void Main(string[] args)
{
var channel=new Grpc.Core.Channel("127.0.0.1",8890,ChannelCredentials.Insecure);
var democlient=new GrpcExampleService.GrpcExampleServiceClient(channel);
var res= democlient.Ask(new AskRequest
{
Cate = 0,
Key = "1312"
});
var res2 = democlient.GetName(new RequestModel
{
Key = "1313"
});
Console.WriteLine($"Ask={res}.GetName={res2}");
}
然後就可以了,啟動服務端,然後再啟動客戶端,就可以看到調用結果了。