gRPC簡介

gRPC is a modern open source high performance RPC framework that can run in any environment。 It can efficiently connect services in and across data centers with pluggable support for load balancing, tracing, health checking and authentication。 It is also applicable in last mile of distributed computing to connect devices, mobile applications and browsers to backend services。

gRPC 是一個高效能、開源和通用的 RPC 框架,面向移動和 HTTP/2 設計。目前提供 C、Java 和 Go 語言版本,分別是:grpc, grpc-java, grpc-go。 其中 C 版本支援 C, C++, Node。js, Python, Ruby, Objective-C, PHP 和 C# 支援。

gRPC 基於 HTTP/2 標準設計,帶來諸如雙向流、流控、頭部壓縮、單 TCP 連線上的多複用請求等特性。這些特性使得其在移動裝置上表現更好,更省電和節省空間佔用。

gRPC是一款RPC框架,在全篇開始之前,我們一起先了解一下

RPC

是什麼?

RPC簡介

RPC(Remote Procedure Call)遠端過程呼叫

,是一種透過網路從遠端計算機程式上請求服務,而不需要了解底層網路技術的協議,簡單的理解是一個節點請求另一個節點提供的服務。

RPC只是一套協議,基於這套協議規範來實現的框架都可以稱為 RPC 框架,比較典型的有 Dubbo、Thrift 和 gRPC。

RPC的特點

RPC 是遠端過程呼叫的方式之一,涉及呼叫方和被呼叫方兩個程序的互動。

因為 RPC 提供類似於本地方法呼叫的形式,所以對於呼叫方來說,

呼叫 RPC 方法和呼叫本地方法並沒有明顯區別

注:

加企鵝群654378476(備註知乎)(資源,原始碼,講師課件,課程諮詢,職業規劃)

每日免費公開課

RPC的前世今生

1984 年,Birrell 和 Nelson 在 ACM Transactions on Computer Systems 期刊上發表了名為

“Implementing remote procedure calls”

的論文,該文對 RPC 的機制做了經典的詮釋:

RPC 遠端過程呼叫是指計算機 A 上的程序,呼叫另外一臺計算機 B 上的程序的方法。其中A 上面的呼叫程序被掛起,而 B 上面的被呼叫程序開始執行對應方法,並將結果返回給 A,計算機 A 接收到返回值後,呼叫程序繼續執行。

發起 RPC 的程序透過引數等方式將資訊傳送給被呼叫方,然後被呼叫方處理結束後,再透過返回值將資訊傳遞給呼叫方。

這一過程對於開發人員來說是透明的,開發人員一般也無須知道雙方底層是如何進行訊息通訊和資訊傳遞的,這樣可以讓業務開發人員更專注於業務開發,而非底層細節。

RPC的呼叫過程

RPC 讓程式之間的遠端過程呼叫具有與本地呼叫類似的形式。比如說某個程式需要讀取某個檔案的資料,開發人員會在程式碼中執行 read 系統呼叫來獲取資料。

當 read 實際是本地呼叫時

,read 函式由連結器從依賴庫中提取出來,接著連結器會將它連結到該程式中。雖然 read 中執行了特殊的系統呼叫,但它本身依然是透過將引數壓入堆疊的常規方式呼叫的,呼叫方並不知道 read 函式的具體實現和行為。

當 read 實際是一個遠端過程時

(比如呼叫遠端檔案伺服器提供的方法),呼叫方程式中需要引入 read 的介面定義,稱為客戶端存根(client-stub)。遠端過程 read 的客戶端存根與本地方法的 read 函式類似,都執行了本地函式呼叫。

不同的是它底層實現上不是進行作業系統呼叫讀取本地檔案來提供資料,而是將引數打包成網路訊息,並將此網路訊息傳送到遠端伺服器,交由遠端服務執行對應的方法,在傳送完呼叫請求後,客戶端存根隨即阻塞,直到收到伺服器發回的響應訊息為止。

下圖展示了遠端方法呼叫過程中的客戶端和服務端各個階段的操作:

看了這篇文章,再也不用花一星期學習“RPC到gRPC”

如上圖所示:一個完整的RPC架構裡面包含了

四個核心的元件

分別是

Client

Client Stub

Server

以及

Server Stub

,這個Stub可以理解為存根。

客戶端(Client),服務的呼叫方。

客戶端存根(Client Stub),存放服務端的地址訊息,再將客戶端的請求引數打包成網路訊息,然後透過網路遠端傳送給服務方。

服務端(Server),真正的服務提供者。

服務端存根(Server Stub),接收客戶端傳送過來的訊息,將訊息解包,並呼叫本地的方法。

當客戶端傳送請求的網路訊息到達伺服器時,伺服器上的網路服務將其傳遞給伺服器存根(server-stub)。伺服器存根與客戶端存根一一對應,是遠端方法在服務端的體現,用來將網路請求傳遞來的資料轉換為本地過程呼叫。伺服器存根一般處於阻塞狀態,等待訊息輸入。

當伺服器存根收到網路訊息後,伺服器將方法引數從網路訊息中提取出來,然後以常規方式呼叫伺服器上對應的實現過程。從實現過程角度看,就好像是由客戶端直接呼叫一樣,引數和返回地址都位於呼叫堆疊中,一切都很正常。

實現過程執行完相應的操作,隨後用得到的結果設定到堆疊中的返回值,並根據返回地址執行方法結束操作。以 read 為例,實現過程讀取本地檔案資料後,將其填充到 read 函式返回值所指向的緩衝區。

read 過程呼叫完後,實現過程將控制權轉移給伺服器存根,它將結果(緩衝區的資料)打包為網路訊息,最後透過網路響應將結果返回給客戶端。網路響應傳送結束後,伺服器存根會再次進入阻塞狀態,等待下一個輸入的請求。

客戶端接收到網路訊息後,客戶作業系統會將該訊息轉發給對應的客戶端存根,隨後解除對客戶程序的阻塞。

客戶端存根從阻塞狀態恢復過來,將接收到的網路訊息轉換為呼叫結果,並將結果複製到客戶端呼叫堆疊的返回結果中。當呼叫者在遠端方法呼叫 read 執行完畢後重新獲得控制權時,它唯一知道的是 read 返回值已經包含了所需的資料,但並不知道該 read 操作到底是在本地作業系統讀取的檔案資料,還是透過遠端過程呼叫遠端服務讀取檔案資料。

總結一下RPC呼叫過程

客戶端(client)以本地呼叫方式(即以介面的方式)呼叫服務;

客戶端存根(client stub)接收到呼叫後,負責將方法、引數等組裝成能夠進行網路傳輸的訊息體(將訊息體物件序列化為二進位制);

客戶端透過sockets將訊息傳送到服務端;

服務端存根( server stub)收到訊息後進行解碼(將訊息物件反序列化);

服務端存根( server stub)根據解碼結果呼叫本地的服務;

本地服務執行並將結果返回給服務端存根( server stub);

服務端存根( server stub)將返回結果打包成訊息(將結果訊息物件序列化);

服務端(server)透過sockets將訊息傳送到客戶端;

客戶端存根(client stub)接收到結果訊息,並進行解碼(將結果訊息發序列化);

客戶端(client)得到最終結果。

RPC的目標是要把2、3、4、7、8、9這些步驟都封裝起來。

注意:無論是何種型別的資料,最終都需要轉換成二進位制流在網路上進行傳輸,資料的傳送方需要將物件轉換為二進位制流,而資料的接收方則需要把二進位制流再恢復為物件。

RPC框架的組成

一個完整的 RPC 框架包含了服務註冊發現、負載、容錯、序列化、協議編碼和網路傳輸等元件。

不同的 RPC 框架包含的元件可能會有所不同,但是一定都包含 RPC 協議相關的元件,RPC 協議包括

序列化、協議編解碼器和網路傳輸棧

,如下圖所示:

看了這篇文章,再也不用花一星期學習“RPC到gRPC”

RPC 協議一般分為公有協議和私有協議。例如,HTTP、SMPP、WebService 等都是公有協議。如果是某個公司或者組織內部自定義、自己使用的,沒有被國際標準化組織接納和認可的協議,往往劃為私有協議,例如 Thrift 協議和螞蟻金服的 Bolt 協議。

分散式架構所需要的企業內部通訊模組,往往採用私有協議來設計和研發。

相較公有協議,私有協議雖然有很多弊端,比如在通用性上、公網傳輸的能力上,但是高度定製化的私有協議可以最大限度地降低成本,提升效能,提高靈活性與效率。

定製私有協議,可以有效地利用協議裡的各個欄位,靈活滿足各種通訊功能需求。

RPC和HTTP區別

RPC 和 HTTP都是微服務間通訊較為常用的方案之一,其實RPC 和 HTTP 並不完全是同一個層次的概念,它們之間還是有所區別的。

RPC 是遠端過程呼叫,其呼叫協議通常包括序列化協議和傳輸協議。

序列化協議有基於純文字的 XML 和 JSON、二進位制編碼的Protobuf和Hessian。傳輸協議是指其底層網路傳輸所使用的協議,比如 TCP、HTTP。

可以看出HTTP是RPC的傳輸協議的一個可選方案,比如說 gRPC 的網路傳輸協議就是 HTTP。

HTTP 既可以和 RPC 一樣作為服務間通訊的解決方案,也可以作為 RPC 中通訊層的傳輸協議(此時與之對比的是 TCP 協議)。

常見的 RPC 框架

目前流行的開源 RPC 框架還是比較多的,有阿里巴巴的 Dubbo、Google 的 gRPC、Facebook 的 Thrift 和 Twitter 的 Finagle 等。

Go RPC:Go 語言原生支援的 RPC 遠端呼叫機制,簡單便捷。

gRPC:Google 釋出的開源 RPC 框架,是基於 HTTP 2。0 協議的,並支援眾多常見的程式語言,它提供了強大的流式呼叫能力,目前已經成為最主流的 RPC 框架之一。

Thrift:Facebook 的開源 RPC 框架,主要是一個跨語言的服務開發框架,作為老牌開源 RPC 協議,以其高效能和穩定性成為眾多開源專案提供資料的方案選項。

gRPC特點

在gRPC的客戶端應用可以想呼叫本地物件一樣直接呼叫另一臺不同的機器上的服務端的應用的物件或者方法,這樣在建立分散式應用的時候更容易。下面看看gRPC的特點:

語言無關,支援多種語言;

基於 IDL 檔案定義服務,透過 proto3 工具生成指定語言的資料結構、服務端介面以及客戶端 Stub;

通訊協議基於標準的 HTTP/2 設計,支援雙向流、訊息頭壓縮、單 TCP 的多路複用、服務端推送等特性,這些特性使得 gRPC 在移動端裝置上更加省電和節省網路流量;

序列化支援 PB(Protocol Buffer)和 JSON,PB 是一種語言無關的高效能序列化框架,基於 HTTP/2 + PB, 保障了 RPC 呼叫的高效能。

gRPC使用說明

gRPC使用和上面RPC使用方法類似,首先定義服務,指定其能夠被遠端呼叫的方法,包括引數和返回型別,這裡使用protobuf來定義服務。

在服務端實現定義的服務介面,並執行一個gRPC伺服器來處理客戶端呼叫。

gRPC安裝

使用git下載:

git clone https://github。com/grpc/grpc-go。git $GOPATH/src/google。golang。org/grpc

git clone https://github。com/golang/net。git $GOPATH/src/golang。org/x/net

git clone https://github。com/golang/text。git $GOPATH/src/golang。org/x/text

git clone https://github。com/google/go-genproto。git $GOPATH/src/google。golang。org/genprot

因為gRPC要使用proto及相關依賴,請先安裝protobuf

都安裝好之後,可以測試gRPC是否可以正常執行。

#進入服務端 (先啟動)

cd /Users/bitliu/go/src/google。golang。org/grpc/examples/helloworld/greeter_server

#進入客戶端端 (服務端啟動後在啟動)

cd /Users/bitliu/go/src/google。golang。org/grpc/examples/helloworld/greeter_client

gRPC四種通訊方式

1、

Simple RPC

簡單rpc

這就是一般的rpc呼叫,一個請求物件對應一個返回物件

rpc simpleHello(Person) returns (Result) {}

2、

Server-side streaming RPC

服務端流式RPC

一個請求物件,服務端可以傳回多個結果物件

rpc serverStreamHello(Person) returns (stream Result) {}

3、

Client-side streaming RPC

客戶端流式RPC

客戶端傳入多個請求物件,服務端返回一個響應結果

rpc clientStreamHello(stream Person) returns (Result) {}

4、

Bidirectional streaming RPC

雙向流式RPC

結合客戶端流式rpc和服務端流式rpc,可以傳入多個物件,返回多個響應物件

rpc biStreamHello(stream Person) returns (stream Result) {}

gRPC案例

下面使用gRPC來實現一個簡單的客戶端和服務端的通訊

1。 先使用protobuf定義服務。

​ 建立myProtobuf。proto檔案,編輯如下內容。

syntax = “proto3” ;

option go_package = “。;myproto”;

//定義服務

service HelloServer {

rpc SayHello (HelloReq) returns (HelloRsp){}

rpc SayName (NameReq) returns (NameRsp){}

}

//客戶端傳送給服務端

message HelloReq {

string name = 1 ;

}

//服務端返回給客戶端

message HelloRsp {

string msg = 1 ;

}

//客戶端傳送給服務端

message NameReq {

string name = 1 ;

}

//服務端返回給客戶端

message NameRsp {

string msg = 1 ;

}

定義了兩個服務SayHello,SayName及對應的四個訊息(message)。

然後在執行命令生成pd。go檔案

protoc ——go_out=plugins=grpc:。/ *。proto #新增grpc外掛

編寫服務端server。go

實現protobuf中的service介面

監聽埠

建立grpc服務

註冊服務

package main

import (

pd “。。/myproto” //匯入proto

“context”

“fmt”

“google。golang。org/grpc”

“net”

type server struct {}

func (this *server) SayHello(ctx context。Context, in *pd。HelloReq) (out *pd。HelloRsp,err error) {

return &pd。HelloRsp{Msg:“Hello”}, nil

}

func (this *server) SayName(ctx context。Context, in *pd。NameReq) (out *pd。NameRsp,err error){

return &pd。NameRsp{Msg:in。Name + “ is my name”}, nil

}

func main() {

ln, err := net。Listen(“tcp”, “:10089”)

if err != nil {

fmt。Println(“network error”, err)

}

//建立grpc服務

srv := grpc。NewServer()

//註冊服務

pd。RegisterHelloServerServer(srv, &server{})

err = srv。Serve(ln)

if err != nil {

fmt。Println(“Serve error”, err)

}

}

編寫客戶端client。go

連線服務端

獲得控制代碼

遠端呼叫函式

package main

import (

pd “。。/myproto” //匯入proto

“context”

“fmt”

“google。golang。org/grpc”

func main() {

//客戶端連線服務端

conn, err := grpc。Dial(“127。0。0。1:10089”, grpc。WithInsecure())

if err != nil {

fmt。Println(“network error”, err)

}

//網路延遲關閉

defer conn。Close()

//獲得grpc控制代碼

c := pd。NewHelloServerClient(conn)

//透過控制代碼進行呼叫服務端函式SayHello

re1, err := c。SayHello(context。Background(),&pd。HelloReq{Name:“bitliu”})

if err != nil {

fmt。Println(“calling SayHello() error”, err)

}

fmt。Println(re1。Msg)

//透過控制代碼進行呼叫服務端函式SayName

re2, err := c。SayName(context。Background(),&pd。NameReq{Name:“BitLiu”})

if err != nil {

fmt。Println(“calling SayName() error”, err)

}

fmt。Println(re2。Msg)

}

如果你看到最後了,請不要忘記點贊,收藏,轉發,更好的是點選上面連結或者加群。謝謝閱讀!!