>[info] 原文地址:http://www.grpc.io/docs/guides/concepts.html
本文介紹gRPC一些核心概念,包含gRPC的架構概述,和RPC的生命周期。
**Overview**
服務定義
類似于許多RPC系統,gRPC的思想基礎也是,定義一個服務,通過參數和返回類型指定可被遠程調用的方法。默認情況下,gRPC使用protocol buffers作為IDL,來描述服務接口,以及消息中有效數據(payload)的結構。如果需要,也可以使用其它選擇。
~~~
service HelloService {
rpc SayHello (HelloRequest) returns (HelloResponse);
}
message HelloRequest {
string greeting = 1;
}
message HelloResponse {
string reply = 1;
}
~~~
gRPC允許你定義4種服務方法:
* 一元RPC,客戶端向服務器發送一個單獨請求,并且獲取一個單一響應,就像一個普通的方法調用。
~~~
rpc SayHello(HelloRequest) returns (HelloResponse){
}
~~~
* 服務端流式RPC,客戶端向服務器發送一個請求,然后得到一個流,可以讀取一個消息序列。客戶端讀取返回的流,直到它沒有任何消息。
~~~
rpc LotsOfReplies(HelloRequest) returns (stream HelloResponse){
}
~~~
* 客戶端流式RPC,客戶端寫一個消息序列,然后使用一個已經提供的流,將它們發送給服務器。當客戶端完成了消息寫入,它會等待服務端讀取它們并返回響應。
~~~
rpc LotsOfGreetings(stream HelloRequest) returns (HelloResponse) {
}
~~~
* 雙向流式RPC,2端都使用一個可讀可寫的流來發送一個消息序列。2個流的操作是獨立的,所以客戶端和服務器可以使用任何順序來讀寫數據:比如,服務器可以等到接收完所有客戶端消息后,再寫入響應,或者它可以讀一條消息然后就寫一條消息,或者其它的讀寫組合。在每個流中的消息都是保序的。
~~~
rpc BidiHello(stream HelloRequest) returns (stream HelloResponse){
}
~~~
我們將在下面的RPC生命周期部分,介紹這幾種RPC的更多細節。
**使用API**
從在proto文件中定義一個服務開始,gRPC提供了protocol buffer編譯插件生成客戶端和服務端方法。gRPC的用戶普遍都是在客戶端調用這些APIs,在服務端實現對應的API。
* 在服務端,服務器實現了服務中定義的方法,并且運行一個gRPC服務器處理客戶端調用。gRPC基礎設施解碼過來的請求,運行服務中的方法,然后將響應進行編碼。
* 在客戶端,客戶端有一個本地對象,叫做stub(在一些語言中,更常用的術語就叫做客戶端),它實現了和服務同樣的方法。客戶端就只需要在本地對象上調用這些方法,將調用的參數包裝進合適的protocol buffer消息類型 -- 由gRPC負責將請求傳送給服務器,并返回服務器的protocol buffer響應。
**同步 vs 異步**
同步RPC調用會阻塞住,直到服務端返回的響應到達,這是RPC期望調用過程的最近似抽象。而另一方面,網絡天生就是異步的,并且在很多場景下,在當前線程中無阻塞的啟動RPCs是很有用的。
在大多數語言的gRPC實現中,都包含了同步和異步2種版本。你可以在各個語言的教程和手冊中找到更多詳情(完整手冊文檔即將發布)
**RPC生命周期**
現在讓我們細致看看,當一個gRPC客戶端調用一個gRPC服務端方法時,會發生什么。我們不看實現細節,你可以在語言規范文檔里面看到關于這些的更多細節。
**一元RPC(Unary RPC)**
首先看看最簡單的RPC類型,客戶端發送一個單獨的請求,然后獲取一個單獨的響應。
* 當客戶端調用本地對象stub上的方法時,服務端就會收到通知說RPC已經被調用,包含有這次調用的客戶端元數據(metadata),方法名,還有指定的期限(deadline)如果可用。
* 服務器可以選擇直接發回它自己的初始元數據(必須在任何響應之前發送),或者等待客戶端的請求消息 -- 先發生那種情況,由應用程序指定。
* 一旦服務器有了客戶端的請求消息,它就開始進行產生并填充響應的相關工作。響應隨即被返回(如果成功)至客戶端,包含了狀態詳情(狀態碼,和可選的狀態消息),以及可選的拖尾元數據(trailing metadata)
* 如果狀態是OK,客戶端會得到響應,然后由客戶端這邊完成本次調用。
**服務端流式RPC(Server streaming RPC)**
一個服務端流式RPC和我們的簡單樣例很相似,除了服務端在收到客戶端請求消息后,返回一個響應流。當返回完它的所有響應后,服務端的狀態詳情(狀態碼,和可選的狀態消息)以及可選的拖尾元數據會被返回,以此完成服務端部分的工作。而客戶端一旦得到服務器的所有響應就會完成本次調用。
**客戶端流式RPC(Client streaming RPC)**
一個客戶端流式RPC也和我們的例子很相似,除了客戶端向服務器發送一個請求流,而不是一個單獨請求。服務器返回一個單獨響應,當服務器獲取到客戶端所有請求后,它一般都會在響應中,附帶上狀態詳情和可選的拖尾元數據,但這不是必需的。
**雙向流式RPC**
在雙向流式RPC中,也同樣是客戶端發起通信,調用方法,并且服務端接收客戶端元數據,方法名,和期限。服務端也同樣是可以選擇發回它的初始元數據,或者是等待客戶端開始發送請求。
接下來發生什么取決于應用程序,由于客戶端和服務器可以以任意順序進行讀寫 -- 流操作是完全獨立的。所以,舉個例子,服務器可以等待直到它收到客戶端的所有消息,然后開始寫響應,或者服務器和客戶端以打乒乓球的方式交流:服務器收到一個請求,然后發回一個響應,然后客戶端基于響應再發起其它請求,以此類推。
**期限【Deadlines】**
gRPC允許客戶端在調用一個遠程方法時指定一個期限值。這個值指定了客戶端會等待服務器響應最長多久,否則本次RPC會以**DEADLINE_EXCEEDED**結束。在服務端,服務器可以查詢這個期限值,來判斷一個特定的方法是否已經超時,或者距離完成還剩多長時間。
期限值如何指定根據語言不同而不同 -- 舉例來說,在Python中期限值總是必需的,并且不是所有語言都有一個默認期限值。
#### RPC終止【RPC termination】
在gRPC中,對于本次調用是否成功,客戶端和服務器都會做出獨立,且本地的決定,它們的結論可能不相匹配。這就意味著,例如,你可能有一個RPC調用,在服務端成功結束(“我已經發送了所有的響應!”),但是在客戶端失敗了(“那些響應都在期限值后才到達”)。再比如,也可能會遇到,在客戶端發完所有請求之前,服務端已決定結束這次RPC。
#### 取消RPCs【Cancelling RPCs】
客戶端或者服務器可以隨時取消一個RPC。取消操作會立即終結RPC以便后續不會再有任何操作。它并不是一個“撤銷”操作:取消操作之前的更改并不會被回滾。當然了,一個同步的RPC方法調用無法被取消,因為直到RPC終止后,程序控制才會返回給應用程序
#### 元數據【Metadata】
元數據是關于一個特定RPC調用的信息(比如[驗證詳情](http://www.grpc.io/grpc.github.io/docs/guides/auth.html))。它的格式是一個鍵-值對的列表,鍵是字符串類型,值通常也是字符串類型(也可以是二進制數據)。元數據對gRPC是不透明的 -- 它讓客戶端提供關于本次調用的信息,并發送給服務器,反之亦然。
訪問元數據也是依賴于具體語言的。
#### 通道【Channels】
一個gRPC通道在指定的地址和端口上,提供了和gRPC服務器的一個連接,并且當創建一個客戶端存根的時候會被用到(在某些語言中直接稱為客戶端)。客戶端可以指定通道參數,來修改gRPC的默認行為,例如開啟和關閉消息壓縮。通道是有狀態的,包括**connected**和**idle**。
gRPC如何關閉通道依賴于具體語言。一些語言也允許查詢通道狀態。