前言
現(xiàn)代的軟件服務(wù)大多數(shù)是分布式應(yīng)用程序茬高,通過暴露自己的 API 對內(nèi)或?qū)ν馓峁┝艘幌盗械墓δ茳c个盆。服務(wù)與服務(wù)之間有時是跨語言矾芙、跨平臺通信的舍沙。
為了解決這些復(fù)雜場景,市面上也涌現(xiàn)了有很多解決方案剔宪。比如構(gòu)建 RESTful 服務(wù)拂铡,將服務(wù)能力轉(zhuǎn)化為資源集合;也有面向函數(shù)調(diào)用的客戶端-服務(wù)器模式:遠程過程調(diào)用(Remote Procedure Calls)葱绒。今天要介紹的 gRPC 就是后者的演變感帅,一個非常受歡迎分布式進程間通信技術(shù)。
認識 gRPC
gRPC 是 Google 在 2015 年推出的 RPC 框架地淀。在認識 gRPC 之前失球,我們先來了解下 RPC 的相關(guān)知識。RPC 主要運用于分布式程序中帮毁,它構(gòu)建了客戶端-服務(wù)器模型实苞,類似于請求-響應(yīng)通信方式,只不過這種請求被我們抽象為了函數(shù)方法 + 入?yún)⑿畔⒘揖危讓拥木W(wǎng)絡(luò)通信則被屏蔽了起來黔牵,到最后就像本地方法調(diào)用一樣。
Protocol Buffers
上面提到了函數(shù)的入?yún)⑿畔⒁危腥雲(yún)⒕陀袑ο箢愋突郑说蕉说膶ο箢愋途蛣荼厣婕暗?br> 網(wǎng)絡(luò)通信過程中的字節(jié)序列化和反序列化過程陆错。在 gRPC 中,采用了 Protobuf(Protocol Buffers)作為序列化和反序列化協(xié)議跃巡。它是一種輕便高效的結(jié)構(gòu)化數(shù)據(jù)存儲格式危号,基于二進制編碼構(gòu)建,能夠減少 CPU 的復(fù)雜解析素邪,保障了 RPC 調(diào)用的高性能。
另外 Protobuf 支持多種編程語言猪半,我們只需要對其進行接口定義描述兔朦,便可以根據(jù)描述文件自動生成客戶端和服務(wù)端需要使用到的結(jié)構(gòu)體和方法函數(shù),就像是代碼自動生成一樣磨确,大大提高了我們的編程效率沽甥。
HTTP/2
gRPC 是基于 HTTP/2 設(shè)計的,HTTP/2 也是 2015 年發(fā)布的乏奥,它是下一代的 HTTP 協(xié)議摆舟,具備很多高級功能,如:
- 基于二進制格式傳輸邓了,傳輸速度更快恨诱,更緊湊,不易出錯骗炉。
- 多路復(fù)用照宝,單個連接可發(fā)送多個請求。
- 對報頭壓縮句葵,能降低傳輸開銷厕鹃。
- 允許服務(wù)器主動推送。
正是這些 HTTP/2 的特性乍丈,使得 gRPC 能夠使用較少的資源剂碴,獲得較快的響應(yīng),在移動端設(shè)備上更加省電省流量轻专。
gRPC 的使用
接口定義
當我們開發(fā)一個 gRPC 應(yīng)用程序時忆矛,要做的第一件事情就是定義一個接口描述文件。在這個文件里铭若,我們會將函數(shù)洪碳、參數(shù)信息都描述出來,就像下面這個 ProductInfo.proto 文件一樣:
// ProductInfo.proto
syntax = "proto3";
package ecommerce;
// 服務(wù)里的接口列表
service ProductInfo {
rpc addProduct(Product) returns (ProductID);
rpc getProduct(ProductID) returns (Product);
}
// 參數(shù)信息
message Product {
string id = 1;
string name = 2;
string description = 3;
}
message ProductID {
string value = 1;
}
gRPC 服務(wù)端
當我們拿到定義好的接口描述文件 ProductInfo.proto 后叼屠,就可以使用 Protobuf 編譯器:protoc 來生成我們的服務(wù)端代碼了瞳腌。假設(shè)我們的服務(wù)端采用的是 Go 語言,則在經(jīng)過一系列插件的安裝后镜雨,我們就可以使用下面的命令來編譯生成代碼了:
protoc --proto_path=IMPORT_PATH --go_out=OUT_DIR --go_opt=paths=source_relative path/to/file.proto
protoc 支持多種語言嫂侍,具體大伙可以按照官方提供的來生成代碼,總之,我們將接口定義文件挑宠,轉(zhuǎn)化為了我們需要的服務(wù)端代碼菲盾,我們只需要引用并實現(xiàn)接口函數(shù)即可為客戶端提供對應(yīng)的服務(wù)了:
import (
...
"context"
pb "github.com/grpc-up-and-running/samples/ch02/productinfo/go/proto"
"google.golang.org/grpc"
...
)
// ProductInfo 接口的實現(xiàn)
// Add product remote method
func (s *server) AddProduct(ctx context.Context, in *pb.Product) (
*pb.ProductID, error) {
// 具體的業(yè)務(wù)邏輯
}
// Get product remote method
func (s *server) GetProduct(ctx context.Context, in *pb.ProductID) (
*pb.Product, error) {
// 具體的業(yè)務(wù)邏輯
}
當然,定義完后還得注冊下服務(wù):
func main() {
lis, _ := net.Listen("tcp", port)
s := grpc.NewServer()
pb.RegisterProductInfoServer(s, &server{})
if err := s.Serve(lis); err != nil {
log.Fatalf("failed to serve: %v", err)
}
}
gRPC 客戶端
與服務(wù)端類似各淀,我們將根據(jù) ProductInfo.proto 文件生成客戶端的代碼懒鉴,即所謂的存根(Stub)。假設(shè)我們的客戶端使用的是 Java 語言碎浇,則我們可以像下面一樣使用 Stub 了:
// 根據(jù)服務(wù)地址創(chuàng)建通道
ManagedChannel channel = ManagedChannelBuilder.forAddress("localhost", 8080)
.usePlaintext(true)
.build();
// 根據(jù)通道創(chuàng)建存根
ProductInfoGrpc.ProductInfoBlockingStub stub =
ProductInfoGrpc.newBlockingStub(channel);
// 調(diào)用存根里的方法
StringValue productID = stub.addProduct(
Product.newBuilder()
.setName("Apple iPhone 11")
.setDescription("Meet Apple iPhone 11." +
"All-new dual-camera system with " +
"Ultra Wide and Night mode.")
.build());
整體流程
可以看到临谱,其實接口定義文件 ProductInfo.proto 就是一個粘合劑,它將服務(wù)端的邏輯抽象成了語言無關(guān)的描述文件奴璃,讓服務(wù)端依次去實現(xiàn)具體邏輯悉默。然后通過它生成的客戶端存根(Stub)則屏蔽了底層的通信流程,只需要暴露讓上層可以調(diào)用的函數(shù)即可苟穆,就像本地函數(shù)調(diào)用一樣抄课。
其他特性
Metadata(元數(shù)據(jù))
gRPC 沒有使用 HTTP 所謂的 header 字段,而是使用 Metadata 來構(gòu)建相關(guān)的頭部信息雳旅。Metadata 是在一次 RPC 調(diào)用過程中關(guān)于這次調(diào)用的信息跟磨,以 key-value 的形式存在。
其中 key 是 string 類型岭辣, value 也是一組 string吱晒。 Metadata 對于 gRPC 本身來說透明,它使得 client 和 server 能為對方提供本次調(diào)用的信息沦童。就像一次 http 請求的 RequestHeader 和 ResponseHeader仑濒,http header 的生命周期是一次 http 請求, Metadata 的生命周期則是一次 RPC 調(diào)用偷遗。
Streaming(流)
得意于 HTTP/2 的多路復(fù)用能力墩瞳,使得 gRPC 的客戶端和服務(wù)端能夠進行流式傳輸,例如我們可以邊傳輸氏豌,邊處理數(shù)據(jù)喉酌。 gRPC 的流式傳輸主要分為了下面幾種:
- 服務(wù)端流式 RPC:客戶端發(fā)送單個請求,服務(wù)器可以發(fā)回多個響應(yīng)泵喘。
- 客戶端流式 RPC:客戶端發(fā)送多個請求泪电,而服務(wù)器只發(fā)回一個響應(yīng)。
- 雙向流式 RPC: 客戶端和服務(wù)器同時相互發(fā)送消息而不等待響應(yīng)纪铺。
Interceptors(攔截器)
gRPC 支持在請求/響應(yīng)中使用攔截功能相速,進行消息的攔截并修改它們,這跟平常我們提到的 HTTP 中間件非常的相似鲜锚⊥晃埽基于攔截器苫拍,我們可以實現(xiàn)類似身份認證、鏈式調(diào)用旺隙、重試等功能绒极,這些對應(yīng)微服務(wù)是非常的契合的。
負載均衡
gRPC 官方提供了關(guān)于負載均衡的方案:Load Balancing in gRPC蔬捷,有興趣的同學(xué)可以自己研究下垄提。
gRPC 優(yōu)點
正是前面的 Protobuf 和 HTTP/2 的底層支持,使得 gRPC 在一推出后就受到了許多人的追捧抠刺,它主要有以下幾個特點:
- 性能:比 REST + JSON 快 10 倍的性能塔淤,消息負載小而緊湊,并且通過壓縮加快了傳輸速度速妖。
- 代碼生成:通過語言無關(guān)的接口定義文件快速的生成各種語言的客戶端、服務(wù)端代碼聪黎,提高了開發(fā)效率罕容。
- 可拓展性好:提高了一體化的 RPC 解決方案,有許多內(nèi)置的解決方案稿饰,例如數(shù)據(jù)交換锦秒、加密、身份驗證喉镰、超時取消旅择、攔截器、負載均衡等侣姆。
gRPC 的缺點
與其他技術(shù)一樣生真,gRPC 有優(yōu)點也有缺點,下面就是我們在開發(fā)應(yīng)用程序時需要注意的點:
- 有限的瀏覽器支持:由于 gRPC 大量使用 HTTP/2捺宗,因此無法之間在 Web 瀏覽器調(diào)用 gRPC 服務(wù)柱蟀,gRPC 比較適用于手機 APP Client。
- 不友好格式:Protobuf 將 gRPC 消息壓縮成非可讀格式蚜厉,需要反序列化才拿到消息格式长已,不好調(diào)試。
總結(jié)
現(xiàn)代軟件應(yīng)用程序已經(jīng)很少孤立存在了昼牛,更多是通過網(wǎng)絡(luò)通信進行服務(wù)溝通术瓮。gRPC 是一種可拓展、松耦合且類型安全的解決方案.
與傳統(tǒng)的基于 REST/HTTP 的通信相比贰健,它能進行更有效的進程間通信胞四,特別是現(xiàn)在流行的微服務(wù)架構(gòu)里,在很多框架里都能看到它的身影存在霎烙。所以撬讽,是時候開始試試它的魅力所在了蕊连!