.NET Core使用gRPC打造服務間通訊基礎設施

1、什麼是RPC

rpc(遠程過程調用)是一個古老而新穎的名詞,他幾乎與http協議同時或更早誕生,也是互聯網數據傳輸過程當中很是重要的傳輸機制。git

利用這種傳輸機制,不一樣進程(或服務)間像調用本地進程中的方法通常進行交互,而無需關心實現細節。github

rpc的主要實現流程爲:web


圖片


一、客戶端本地方法調用客戶端stub(方法存根)。這個調用發生在客戶端本地,並把調用參數推送到棧中。docker

二、客戶端stub (方法存根)將這些參數打包,經過系統調用發送到服務器機器。打包的過程一般能夠採用xml、json、二進制編碼。打包的過程被稱爲marshalling。json

三、客戶端本地操做系統發送信息到目標服務器(能夠經過自定義tcp協議或Http協議傳輸)。後端

四、服務器系統將信息傳送到服務端stub(方法存根)數組

五、服務端stub (服務端方法存根) 解析信息。解析信息的過程能夠稱爲 unmarshalling。瀏覽器

六、服務器stub (服務端方法存根) 調用程序,並經過相似的方式返回客戶端。服務器

爲了讓不一樣的客戶端均能訪問服務器,許多標準化的rpc組件每每會使用接口描述語言的形式,以便方便跨平臺、跨語言的遠程過程調用的實現。微信

圖1:RPC 調用流程

參考維基百科:https://zh.wikipedia.org/wiki/%E9%81%A0%E7%A8%8B%E9%81%8E%E7%A8%8B%E8%AA%BF%E7%94%A8

2、何時使用RPC?

HTTP和RPC是現代微服務架構中廣泛採用的兩種數據傳輸方式,在某種場合幾乎都是能夠徹底替換的,但又具備各自不一樣的特色。

一、HTTP協議是一種規範、開放、通用性很是強、標準的傳輸協議,幾乎全部的語言都支持,若是要確保各種平臺都能無縫的訪問數據,能夠考慮使用HTTP協議。例如目前經常使用的RestFul規約,定義好請求方法、數據格式並以Json的形式返回參數,可以讓先後端之間的對接很是便捷;以前的開發者或許用wsdl、soap的形式比較多,也都是HTTP協議的應用。

二、RPC協議不只僅是一種服務間傳輸的協議,也能使用於進程間的數據傳輸,它能極大的下降微服務間的通訊成本,屏蔽通訊細節,讓調用者可以像調用本地方法通常調用遠程方法。相對而言,RPC可能沒法在網頁端提供支持,也並不是全部的語言都實現了這種接口描述語言,讓開發過程會相對繁瑣,所以它的使用範圍相對較小。雖然gRPC目前已經提供了web版的gRPC,但因爲瀏覽器的兼容性等問題,也限制了他的應用。

3、什麼是gRPC

gRPC能夠通俗的理解爲google實現的一種 RPC的形式。

參見gRPC官網的解釋:

gRPC是能夠在任何環境中運行的現代開源高性能RPC框架。它能夠經過可插拔的支持來有效地鏈接數據中心內和跨數據中心的服務,以實現負載平衡,跟蹤,運行情況檢查和身份驗證。它也適用於分佈式計算的最後一英里,以將設備,移動應用程序和瀏覽器鏈接到後端服務。

它包括四個主要特色:

1.簡單的服務定義:gRPC基於Protobuf協議構建,該協議提供了一個強大的二進制序列化工具集和語言定義服務。2.跨語言和平臺工做:可自動爲多語言或平臺生成符合相應習慣的客戶端和服務端存根3.快速啓動並擴展:只需一行代碼便可安裝運行時環境和生成環境、並經過該框架可擴展到數百萬rpc請求。4.雙向流和集成身份驗證:基於http/2的傳輸機制以及雙向流傳輸和徹底集成的可插入式身份驗證機制。

gRPC目前普遍應用於各大互聯網公司的微服務架構中,也是CNCF基金會孵化的開源基礎設施組件。其官網爲https://grpc.io/;開源項目地址爲https://github.com/grpc/grpc。

官網提供了詳細的文檔說明,幾乎能夠開箱即用,只需簡單配置就能知足你的應用需求。在開源項目中也提供了完善的各類語言實現的sample示例代碼,能極大的方便開發者的使用。

在gRPC中,使用的傳輸協議爲HTTP/2,使用的數據傳輸的格式爲Protobuf協議。

4、什麼是Protobuf

Protobuf全稱爲Protocal Buffers,是一種序列化協議實現,與只相似的還有thrift。這是一種與語言中立、與實現無關、可擴展的序列化數據格式,不只僅能夠用於通訊協議傳輸過程,也一樣適用於數據存儲過程。它靈活高效、性能優良、更加快速和簡單。在使用Protobuf的實踐中,只需定義要處理數據的數據結構,就能利用Protobuf生成相關的代碼。只需使用Protobuf對數據結構進行描述(IDL),便可在各類不一樣的語言或不一樣的數據流中對結構化數據進行輕鬆讀寫。

在上面的圖1 RPC調用流程中,使用紅色字體標註的(1)中,在客戶端套接字和服務端套接字之間進行數據交換的數據傳輸機制就可使用Protobuf。

Protocol Buffers最先是有谷歌發明用於解決索引服務器之間request/response協議的。經過慢慢發展發展和演進,目前已經具備了更多的特性:

自動生成的序列化和反序列化代碼避免了手動解析的須要。(官方提供自動生成代碼工具,各個語言平臺的基本都有)除了用於 RPC(遠程過程調用)請求以外,人們開始將 protocol buffers 用做持久存儲數據的便捷自描述格式(例如,在Bigtable中)。服務器的 RPC 接口能夠先聲明爲協議的一部分,而後用 protocol compiler 生成基類,用戶可使用服務器接口的實際實現來覆蓋它們。

因爲protocal buffers誕生之初主要是爲了解決服務器新舊協議之間兼容性問題,因此命名爲"協議緩衝區",不過目前顯然已經超出了緩衝含義的範圍。而Protobuf中的術語,則使用"message"來指代在協議傳輸過程當中定義的抽象化對象,也顯然再也不僅僅只是原始含義的消息所能囊括的。

5、Proto3協議簡述

當咱們使用Visual Studio 2019建立一個.NET Core下的gRPC項目時,能夠看到,項目會自帶一個Protos\Greet.proto文件,這即是gRPC使用的Protobuf的接口描述文件,經過定義這個描述文件,能夠爲生成對應的服務端、客戶端方法存根,讓方法調用過程更加簡單。

一、基本的數據類型對應關係

目前最新版本的Protobuf協議爲proto3協議,在這個新版的協議中,提供瞭如下數據類型,能夠方便的對應到咱們平常使用的數據類型。


圖片


二、關鍵字

1)分配字段編號

在proto協議中,每一個消息定義中的字段都有惟一的編號,用來表示消息二進制格式中的字段,且使用消息類型後不該更改。可使用的最小編號爲1,最大編號爲2^29^-1 或 536,870,911,但不包括 19000 到 19999(FieldDescriptor :: kFirstReservedNumber 到 FieldDescriptor :: kLastReservedNumber),由於它們是爲 Protocol Buffers實現保留的。

2)重複字段(repeated)

在消息中定義重複字段(repeated 關鍵字),容許一個message 字段中重複數值,能夠理解爲數組對象。

3)保留字段(reserved)

Protobuf中提供了保留字段(reserved 關鍵字),若是在老版本的proto文件中定義了一些字段,而在新版本的協議中移除了這些字段,有可能出現協議文件不匹配的問題,則可使用reserved關鍵字。這樣當協議數據不匹配時,編譯器會提示錯誤。


圖片


圖2 使用保留字段時,會提示錯誤

三、枚舉

容許在消息中定義枚舉類型。也能夠將枚舉類型嵌套在message中。當使用枚舉類型時,須要注意:

枚舉爲 0 的是做爲零值,當不賦值的時候,就會是零值。爲了和 proto2 兼容。在 proto2 中,零值必須是第一個值。

四、消息嵌套

在proto協議中,容許嵌套組合爲更加複雜的消息。


message SearchResponse { repeated Result results = 1;}message Result { string url = 1; string title = 2; repeated string snippets = 3;}

五、定義服務(Services)

在proto中,若是須要對外提供接口方法,則須要使用Services。定義好services以後,protocol buffer編譯器將使用所選語言生成服務接口代碼和客戶端與服務端方法存根。例如,

service Greeter { // Sends a greeting rpc SayHello (HelloRequest) returns (HelloReply);}

這裏就定義了一個SayHello的方法存根。該方法將返回一個名稱爲 HelloReply 的消息。若是須要定義無參數方法,或返回值爲 void 的方法,須要使用 google.protobuf.Empty對象 ,表示傳輸空消息是空的JSON對象{}並在頭部的命名空間中,引用默認的協議文件

google/protobuf/empty.proto. *例如:


option csharp_namespace = "TestGRPC_Client";import "google/protobuf/empty.proto";package Greet;// The greeting service definition.service Greeter { // Sends a greeting rpc SayHello (HelloRequest) returns (HelloReply); rpc Listen (google.protobuf.Empty) returns (google.protobuf.Empty);//啓動監聽}

一樣,也可使用 import 引用其餘協議文件。參考《https://github.com/halfrost/Halfrost-Field/blob/master/contents/Protocol/Protocol-buffers-encode.md》

6、什麼是HTTP/2

gRPC 的客戶端與服務端之間的通訊機制,並無採用TCP造輪子,而是重用了HTTP/2的傳輸協議。HTTP2.0是超文本傳輸協議HTTP的下一代協議,也是在傳統開發者最爲熟悉的HTTP/1.1 協議格式基礎上進行的升級。與1.1相比,他提供了新的二進制格式、多路複用機制、Header壓縮、服務端推送等特點,讓協議請求過程可以達到更好的性能提高。限於篇幅,這裏就再也不贅述了。

7、服務端開發

咱們將引入一個範例,以HelloWorld做爲項目名稱,在這個項目中,簡單介紹在NetCore中如何使用gRPC的過程、如何使用gRPC進行簡單身份驗證的過程。

服務端:

並提供一個登陸 login 的方法、以及其配套的用戶請求參數、並返回對應的響應值。一個 logout 的方法,該方法返回值爲void空對象。

一、建立項目

在Visual Studio 2019中建立一個基於gRPC的空項目。這個項目命名爲HelloWorld,放置在默認目錄下。若是有須要能夠開啓容器支持。



圖片



圖片



圖片


二、項目的組成結構

當咱們查看這個項目時,能夠看到這是一個Asp.NET Core的項目,默認的項目模板中已經集成了Asp.NET Core和gRPC.AspNetCore的組件包。


圖片


Protos文件夾

後綴名爲proto的是基於proto3的協議文件。


圖片


Services文件夾

項目模板建立的默認請求文件,實現了在proto文件中定義的SayHello方法,並以異步的形式返回了對象HelloReply。


圖片


其餘文件

Dockerfile:模板自動建立的Dockerfile文件,後期能夠基於這個文件進行docker容器的構建。

Program: 程序運行的入口。

Startup: 程序啓動項,定義AspNET Core項目啓動所需的各類配置信息。在UseRouting和UseEndPoints中間,加入UseAuthentication()和UseAuthorization()代碼,以便爲後期身份認證和受權。。


圖片


三、建立Proto文件

在Protos文件夾右鍵單擊,建立一個空的記事本文件(快捷鍵爲Ctrl+Shift+A),命名爲helloworld.proto。而後再裏面鍵入如下內容:

syntax = "proto3"import "google/protobuf/empty.proto"; //須要使用空參數和空返回值時,須要使用這個默認的協議文件 option csharp_namespace="HelloWorld";package Account;service Account{ rpc Login (LoginModel) returns (UserModel); rpc Logout (google.protobuf.Empty) returns (google.protobuf.Empty);}message LoginModel{ string userName=1; string userPsw=2;}message UserModel{ string NickName=1; string Token=2; Date LoginDate=3;}message Date{ int32 Year=1; int32 Month=2; int32 Day=3; int32 Hour=4; int32 Minute=5; int32 Second=6; int32 FFF=7; }

四、建立 AccountService文件

選擇 Services 文件夾,並建立一個文件名爲 AccountService的CSharp代碼文件。並分別重載 Login 和 Logout 方法。

 public class AccountService : account.accountBase { public override Task<UserModel> Login(LoginModel request, ServerCallContext context) { return base.Login(request, context); } public override Task<Empty> Logout(Empty request, ServerCallContext context) { return base.Logout(request, context); } }

而後再進行代碼的編寫。這裏咱們將登陸後,返回一個假的 UserModel 數據。除此以外,咱們還返回了錯誤狀況下的返回模型 BadRequest 。

public class AccountService : account.accountBase{ public override Task<StringData> Login(LoginModel request, ServerCallContext context) { if (request.UserName == "1234" && request.UserPsd == "1234") { var userModel = new UserModel { NickName = "測試用戶", Token = Guid.NewGuid().ToString(), };  return Task.FromResult(new StringData() { Data = Newtonsoft.Json.JsonConvert.SerializeObject(userModel) }); } else { var BadRequest = new BadRequest { ErrorCode = 1, ErrorDescription = "用戶名或密碼錯誤" }; return Task.FromResult(new StringData() { Data = Newtonsoft.Json.JsonConvert.SerializeObject(BadRequest) }); } } public override Task<Empty> Logout(Empty request, ServerCallContext context) { return Task.FromResult(new Empty()); }}public class BadRequest{ public int ErrorCode { get; set; } public string ErrorDescription { get; set; }}

8、客戶端開發

客戶端,是一個基於 .NET Core 的控制檯程序。在這個控制檯中,咱們能夠實現下面功能:

經過輸入命令 1 調用登陸方法;輸入命令 2 調用登出方法。

一、建立項目、引用依賴包

建立一個基於.NET Core的一個控制檯程序,並使用 Nuget 安裝組件包



圖片


二、建立協議文件

將在服務端開發中建立的 Protos 文件夾拷貝到客戶端程序中。


圖片


並使用記事本對項目文件【HelloWorld.Client.csproj】進行編輯, 將Protobuf 文件的GrpcServices屬性設置爲 「Client」。

完成這些操做,編譯完成,便可自動生成客戶端與服務端鏈接的客戶端方法存根。

三、編寫客戶端方法

建立一個單獨的類文件,用來編寫客戶端調用方法。這個類文件名稱爲 AccountClientImpl。代碼以下:

using Grpc.Net.Client;using System;using System.Collections.Generic;using System.Text;using static HelloWorld.Greeter;using System.Threading.Tasks;namespace HelloWorld.Client{ public class AccountClientImpl { private readonly GrpcChannel _grpcChannel; private readonly Account.AccountClient _accountClient; public AccountClientImpl(GrpcChannel grpcChannel, Account.AccountClient accountClient) { _grpcChannel = grpcChannel; _accountClient = accountClient; } public void Login() { var result = _accountClient.Login(new LoginModel() { UserName = "1234", UserPsd = "1234" }); Console.WriteLine(result.Data); } public void Logout() { var empty = new Google.Protobuf.WellKnownTypes.Empty(); _accountClient.Logout(empty); } }

}

而後再修改 Program.cs 文件,用來調用上述方法。在這個方法中,若是輸入1,則執行登陸方法;輸入2,則執行退出方法。

class Program{ static void Main(string[] args) { var channel = GrpcChannel.ForAddress("https://localhost:5001"); var client = new Account.AccountClient(channel);
AccountClientImpl accountClientImpl = new AccountClientImpl(channel, client); if (Console.ReadLine() == "1") { accountClientImpl.Login(); } else if (Console.ReadLine() == "2") { accountClientImpl.Logout(); } Console.ReadKey(); }}

這樣就完成了咱們的代碼編寫。將客戶端與服務端運行起來,而後在客戶端代碼中輸入數字 1 ;便可得到咱們想要的結果。

圖片

9、協議與項目分離

在傳統的開發過程當中,因爲客戶端和服務端須要維護兩套內容徹底相同的 proto 協議文件,略顯臃腫,所以咱們能夠經過相應的手段,將對應的文件進行分離,便於後期的維護。

一、移動文件

將服務端中的Protos文件移動到上一級目錄。


圖片


二、修改項目文件中的Proto文件

服務端修改成:

 <ItemGroup> <Protobuf Include="..\Protos\*.proto" GrpcServices="Server" />  <Content Include="@(Protobuf)" LinkBase="" /> </ItemGroup> 

客戶端修改成:

 <ItemGroup> <Protobuf Include="..\Protos\*.proto" GrpcServices="Client" />  <Content Include="@(Protobuf)" LinkBase="" /> </ItemGroup> 

圖片

若是以爲這樣的展現效果不太美觀,也能夠將proto文件移動到Protos目錄下。

三、從新編譯

完成協議文件分離,便可對項目進行編譯。

總結

在這個教程中,咱們從PRC開始講起,簡單介紹了與gRPC相關的技術棧,練習了使用 gRPC 進行服務端和客戶端程序開發的全過程,但願你們能得到收穫。

第一次嘗試編寫入門級教程,若有不足之處還請批評指正。


本文分享自微信公衆號 - DotNet技術平臺(DotNetCore_Moments)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索