新聞中心
五分鐘學(xué)會(huì) gRPC,你學(xué)會(huì)了嗎?
作者:crossoverJie 2022-03-08 08:39:22
云計(jì)算
云原生 gRPC 內(nèi)容還是非常多的,本文只是作為一份入門資料希望能讓不了解 gRPC 的能有一個(gè)基本認(rèn)識(shí);這在云原生時(shí)代確實(shí)是一門必備技能。

10余年的合陽網(wǎng)站建設(shè)經(jīng)驗(yàn),針對(duì)設(shè)計(jì)、前端、開發(fā)、售后、文案、推廣等六對(duì)一服務(wù),響應(yīng)快,48小時(shí)及時(shí)工作處理。成都營銷網(wǎng)站建設(shè)的優(yōu)勢是能夠根據(jù)用戶設(shè)備顯示端的尺寸不同,自動(dòng)調(diào)整合陽建站的顯示方式,使網(wǎng)站能夠適用不同顯示終端,在瀏覽器中調(diào)整網(wǎng)站的寬度,無論在任何一種瀏覽器上瀏覽網(wǎng)站,都能展現(xiàn)優(yōu)雅布局與設(shè)計(jì),從而大程度地提升瀏覽體驗(yàn)。成都創(chuàng)新互聯(lián)公司從事“合陽網(wǎng)站設(shè)計(jì)”,“合陽網(wǎng)站推廣”以來,每個(gè)客戶項(xiàng)目都認(rèn)真落實(shí)執(zhí)行。
介紹
我猜測大部分長期使用 Java 的開發(fā)者應(yīng)該較少會(huì)接觸 gRPC,畢竟在 Java 圈子里大部分使用的還是 Dubbo/SpringClound 這兩類服務(wù)框架。
我也是近段時(shí)間有機(jī)會(huì)從零開始重構(gòu)業(yè)務(wù)才接觸到 gRPC 的,當(dāng)時(shí)選擇gRPC 時(shí)也有幾個(gè)原因:
- 基于云原生的思路開發(fā)部署項(xiàng)目,而在云原生中 gRPC 幾乎已經(jīng)是標(biāo)準(zhǔn)的通訊協(xié)議了。
- 開發(fā)語言選擇了 Go,在 Go 圈子中 gRPC 顯然是更好的選擇。
- 公司內(nèi)部有部分業(yè)務(wù)使用的是 Python 開發(fā),在多語言兼容性上 gRPC 支持的非常好。
經(jīng)過線上一年多的平穩(wěn)運(yùn)行,可以看出 gRPC 還是非常穩(wěn)定高效的;rpc 框架中最核心的幾個(gè)要點(diǎn):
- 序列化
- 通信協(xié)議
- IDL(接口描述語言)
這些在 gRPC 中分別對(duì)應(yīng)的是:
- 基于 Protocol Buffer 序列化協(xié)議,性能高效。
- 基于 HTTP/2 標(biāo)準(zhǔn)協(xié)議開發(fā),自帶 stream、多路復(fù)用等特性;同時(shí)由于是標(biāo)準(zhǔn)協(xié)議,第三方工具的兼容性會(huì)更好(比如負(fù)載均衡、監(jiān)控等)。
- 編寫一份 .proto 接口文件,便可生成常用語言代碼。
HTTP/2
學(xué)習(xí) gRPC 之前首先得知道它是通過什么協(xié)議通信的,我們?nèi)粘2还苁情_發(fā)還是應(yīng)用基本上接觸到最多的還是 HTTP/1.1 協(xié)議。
由于 HTTP/1.1 是一個(gè)文本協(xié)議,對(duì)人類非常友好,相反的對(duì)機(jī)器性能就比較低。
需要反復(fù)對(duì)文本進(jìn)行解析,效率自然就低了;要對(duì)機(jī)器更友好就得采用二進(jìn)制,HTTP/2 自然做到了。
除此之外還有其他優(yōu)點(diǎn):
- 多路復(fù)用:可以并行的收發(fā)消息,互不影響。
- HPACK 節(jié)省 header 空間,避免 HTTP1.1 對(duì)相同的 header 反復(fù)發(fā)送。
Protocol
gRPC 采用的是 Protocol 序列化,發(fā)布時(shí)間比 gRPC 早一些,所以也不僅只用于 gRPC,任何需要序列化 IO 操作的場景都可以使用它。
它會(huì)更加的省空間、高性能;之前在開發(fā) https://github.com/crossoverJie/cim 時(shí)就使用它來做數(shù)據(jù)交互。
package order.v1;
service OrderService{
rpc Create(OrderApiCreate) returns (Order) {}
rpc Close(CloseApiCreate) returns (Order) {}
// 服務(wù)端推送
rpc ServerStream(OrderApiCreate) returns (stream Order) {}
// 客戶端推送
rpc ClientStream(stream OrderApiCreate) returns (Order) {}
// 雙向推送
rpc BdStream(stream OrderApiCreate) returns (stream Order) {}
}
message OrderApiCreate{
int64 order_id = 1;
repeated int64 user_id = 2;
string remark = 3;
repeated int32 reason_id = 4;
}
使用起來也是非常簡單的,只需要定義自己的 .proto 文件,便可用命令行工具生成對(duì)應(yīng)語言的 SDK。
具體可以參考官方文檔:https://grpc.io/docs/languages/go/generated-code/
調(diào)用
protoc --go_out=. --go_opt=paths=source_relative \
--go-grpc_out=. --go-grpc_opt=paths=source_relative \
test.proto
生成代碼之后編寫服務(wù)端就非常簡單了,只需要實(shí)現(xiàn)生成的接口即可。
func (o *Order) Create(ctx context.Context, in *v1.OrderApiCreate) (*v1.Order, error) {
// 獲取 metadata
md, ok := metadata.FromIncomingContext(ctx)
if !ok {
return nil, status.Errorf(codes.DataLoss, "failed to get metadata")
}
fmt.Println(md)
fmt.Println(in.OrderId)
return &v1.Order{
OrderId: in.OrderId,
Reason: nil,
}, nil
}
客戶端也非常簡單,只需要依賴服務(wù)端代碼,創(chuàng)建一個(gè) connection 然后就和調(diào)用本地方法一樣了。
這是經(jīng)典的 unary(一元)調(diào)用,類似于 http 的請(qǐng)求響應(yīng)模式,一個(gè)請(qǐng)求對(duì)應(yīng)一次響應(yīng)。
Server stream
gRPC 除了常規(guī)的 unary 調(diào)用之外還支持服務(wù)端推送,在一些特定場景下還是很有用的。
func (o *Order) ServerStream(in *v1.OrderApiCreate, rs v1.OrderService_ServerStreamServer) error {
for i := 0; i < 5; i++ {
rs.Send(&v1.Order{
OrderId: in.OrderId,
Reason: nil,
})
}
return nil
}
服務(wù)端的推送如上所示,調(diào)用 Send 函數(shù)便可向客戶端推送。
for {
msg, err := rpc.RecvMsg()
if err == io.EOF {
marshalIndent, _ := json.MarshalIndent(msgs, "", "\t")
fmt.Println(msg)
return
}
}
客戶端則通過一個(gè)循環(huán)判斷當(dāng)前接收到的數(shù)據(jù)包是否已經(jīng)截止來獲取服務(wù)端消息。
為了能更直觀的展示這個(gè)過程,優(yōu)化了之前開發(fā)的一個(gè) gRPC 客戶端,可以直觀的調(diào)試 stream 調(diào)用。
上圖便是一個(gè)服務(wù)端推送示例。
Client Stream
除了支持服務(wù)端推送之外,客戶端也支持。
客戶端在同一個(gè)連接中一直向服務(wù)端發(fā)送數(shù)據(jù),服務(wù)端可以并行處理消息。
// 服務(wù)端代碼
func (o *Order) ClientStream(rs v1.OrderService_ClientStreamServer) error {
var value []int64
for {
recv, err := rs.Recv()
if err == io.EOF {
rs.SendAndClose(&v1.Order{
OrderId: 100,
Reason: nil,
})
log.Println(value)
return nil
}
value = append(value, recv.OrderId)
log.Printf("ClientStream receiv msg %v", recv.OrderId)
}
log.Println("ClientStream finish")
return nil
}
// 客戶端代碼
for i := 0; i < 5; i++ {
messages, _ := GetMsg(data)
rpc.SendMsg(messages[0])
}
receive, err := rpc.CloseAndReceive()
代碼與服務(wù)端推送類似,只是角色互換了。
Bidirectional Stream
同理,當(dāng)客戶端、服務(wù)端同時(shí)都在發(fā)送消息也是支持的。
// 服務(wù)端
func (o *Order) BdStream(rs v1.OrderService_BdStreamServer) error {
var value []int64
for {
recv, err := rs.Recv()
if err == io.EOF {
log.Println(value)
return nil
}
if err != nil {
panic(err)
}
value = append(value, recv.OrderId)
log.Printf("BdStream receiv msg %v", recv.OrderId)
rs.SendMsg(&v1.Order{
OrderId: recv.OrderId,
Reason: nil,
})
}
return nil
}
// 客戶端
for i := 0; i < 5; i++ {
messages, _ := GetMsg(data)
// 發(fā)送消息
rpc.SendMsg(messages[0])
// 接收消息
receive, _ := rpc.RecvMsg()
marshalIndent, _ := json.MarshalIndent(receive, "", "\t")
fmt.Println(string(marshalIndent))
}
rpc.CloseSend()
其實(shí)就是將上訴兩則合二為一。
通過調(diào)用示例很容易理解。
元數(shù)據(jù)
gRPC 也支持元數(shù)據(jù)傳輸,類似于 HTTP 中的 header。
// 客戶端寫入
metaStr := `{"lang":"zh"}`
var m map[string]string
err := json.Unmarshal([]byte(metaStr), &m)
md := metadata.New(m)
// 調(diào)用時(shí)將 ctx 傳入即可
ctx := metadata.NewOutgoingContext(context.Background(), md)
// 服務(wù)端接收
md, ok := metadata.FromIncomingContext(ctx)
if !ok {
return nil, status.Errorf(codes.DataLoss, "failed to get metadata")
}
fmt.Println(md)
gRPC gateway
gRPC 雖然功能強(qiáng)大使用也很簡單,但對(duì)于瀏覽器、APP的支持還是不如 REST 應(yīng)用廣泛(瀏覽器也支持,但應(yīng)用非常少)。
為此社區(qū)便創(chuàng)建了 https://github.com/grpc-ecosystem/grpc-gateway 項(xiàng)目,可以將 gRPC 服務(wù)暴露為 RESTFUL API。
為了讓測試可以習(xí)慣用 postman 進(jìn)行接口測試,我們也將 gRPC 服務(wù)代理出去,更方便的進(jìn)行測試。
反射調(diào)用
作為一個(gè) rpc 框架,泛化調(diào)用也是必須支持的,可以方便開發(fā)配套工具;gRPC 是通過反射支持的,通過拿到服務(wù)名稱、pb 文件進(jìn)行反射調(diào)用。
https://github.com/jhump/protoreflect 這個(gè)庫封裝了常見的反射操作。
上圖中看到的可視化 stream 調(diào)用也是通過這個(gè)庫實(shí)現(xiàn)的。
負(fù)載均衡
由于 gRPC 是基于 HTTP/2 實(shí)現(xiàn)的,客戶端和服務(wù)端會(huì)保持長連接;這時(shí)做負(fù)載均衡就不像是 HTTP 那樣簡單了。
而我們使用 gRPC 想達(dá)到效果和 HTTP 是一樣的,需要對(duì)請(qǐng)求進(jìn)行負(fù)載均衡而不是連接。
通常有兩種做法:
- 客戶端負(fù)載均衡
- 服務(wù)端負(fù)載均衡
客戶端負(fù)載均衡在 rpc 調(diào)用中應(yīng)用廣泛,比如 Dubbo 就是使用的客戶端負(fù)載均衡。
gRPC 中也提供有相關(guān)接口,具體可以參考官方demo。
??https://github.com/grpc/grpc-go/blob/87eb5b7502/examples/features/load_balancing/README.md??
客戶端負(fù)載均衡相對(duì)來說對(duì)開發(fā)者更靈活(可以自定義適合自己的策略),但相對(duì)的也需要自己維護(hù)這塊邏輯,如果有多種語言那就得維護(hù)多份。
所以在云原生這個(gè)大基調(diào)下,更推薦使用服務(wù)端負(fù)載均衡。
可選方案有:
- istio
- envoy
- apix
這塊我們也在研究,大概率會(huì)使用 envoy/istio。
總結(jié)
gRPC 內(nèi)容還是非常多的,本文只是作為一份入門資料希望能讓不了解 gRPC 的能有一個(gè)基本認(rèn)識(shí);這在云原生時(shí)代確實(shí)是一門必備技能。
對(duì)文中的 gRPC 客戶端感興趣的朋友,可以參考這里的源碼:https://github.com/crossoverJie/ptg。
文章名稱:五分鐘學(xué)會(huì)gRPC,你學(xué)會(huì)了嗎?
文章來源:http://www.dlmjj.cn/article/ccsceje.html


咨詢
建站咨詢
