一個簡單的案例入門 gRPC
這篇文章本來要在年前和小伙伴們見面,但是因為我之前的 Mac 系統(tǒng)版本是 10.13.6,這個版本比較老,時至今天在運行一些新鮮玩意的時候有時候會有一些 BUG(例如運行最新版的 Nacos 等),運行 gRPC 的插件也有 BUG,代碼總是生成有問題,但是因為系統(tǒng)升級是一個大事,所以一直等到過年放假,在家才慢慢折騰將 Mac 升級到目前的 13.1 版本,之前這些問題現(xiàn)在都沒有了,gRPC 的案例現(xiàn)在也可以順利跑起來了。
所以今天就來和小伙伴們簡單聊一聊 gRPC。
1. 緣起
我為什么想寫一篇 gRPC 的文章呢?其實本來我是想和小伙伴們梳理一下在微服務(wù)中都有哪些跨進城調(diào)用的方式,在梳理的過程中想到了 gRPC,發(fā)現(xiàn)還沒寫文章和小伙伴們聊過 gRPC,因此打算先來幾篇文章和小伙伴們詳細(xì)介紹一下 gRPC,然后再梳理微服務(wù)中的跨進程方案。
2. 什么是 gRPC
了解 gRPC 之前先來看看什么是 RPC。
RPC 全稱是 Remote Procedure Call,中文一般譯作遠程過程調(diào)用。RPC 是一種進程間的通信模式,程序分布在不同的地址空間里。簡單來說,就是兩個進程之間互相調(diào)用的一種方式。
gRPC 則是一個由 Google 發(fā)起的開源的 RPC 框架,它是一個高性能遠程過程調(diào)用 (RPC) 框架,可以在任何環(huán)境中運行。gRPC 通過對負(fù)載均衡、跟蹤、健康檢查和身份驗證的可插拔支持,有效地連接數(shù)據(jù)中心內(nèi)和數(shù)據(jù)中心之間的服務(wù)。
在 gRPC 中,客戶端應(yīng)用程序可以直接調(diào)用部署在不同機器上的服務(wù)端應(yīng)用程序中的方法,就好像它是本地對象一樣,使用 gRPC 可以更容易地創(chuàng)建分布式應(yīng)用程序和服務(wù)。與許多 RPC 系統(tǒng)一樣,gRPC 基于定義服務(wù)的思想,指定基于參數(shù)和返回類型遠程調(diào)用的方法。在服務(wù)端側(cè),服務(wù)端實現(xiàn)接口,運行 gRPC 服務(wù),處理客戶端調(diào)用。在客戶端側(cè),客戶端擁有存根(Stub,在某些語言中稱為客戶端),它提供與服務(wù)端相同的方法。
gRPC 客戶端和服務(wù)端可以在各種環(huán)境中運行和相互通信 – 從 Google 內(nèi)部的服務(wù)器到你自己的桌面 – 并且可以使用 gRPC 支持的任何語言編寫。因此,你可以輕松地用 Java 創(chuàng)建 gRPC 服務(wù)端,使用 Go、Python 或 Ruby 創(chuàng)建客戶端。此外,最新的 Google API 將包含 gRPC 版本的接口,使你輕松地將 Google 功能構(gòu)建到你的應(yīng)用程序中。
gRPC 支持的語言版本:
說了這么多,還是得整兩個小案例小伙伴們可能才會清晰,所以我們也不廢話了,上案例。
3. 實踐
先來看下我們的項目結(jié)構(gòu):
大家看下,這里首先有一個 grpc-api,這個模塊用來放我們的公共代碼;grpc-server 是我們的服務(wù)端,grpc-client 則是我們的客戶端,這些都是普通的 maven 項目。
3.1 grpc-api
在 grpc-api 中,我們首先引入項目依賴,如下:
除了這些常規(guī)的依賴之外,還需要一個插件:
我來說一下這個插件的作用。
默認(rèn)情況下,gRPC 使用 Protocol Buffers,這是 Google 提供的一個成熟的開源的跨平臺的序列化數(shù)據(jù)結(jié)構(gòu)的協(xié)議,我們編寫對應(yīng)的 proto 文件,通過上面這個插件可以將我們編寫的 proto 文件自動轉(zhuǎn)為對應(yīng)的 Java 類。
多說一句,使用 Protocol Buffers 并不是必須的,也可以使用 JSON 等,但是目前來說這個場景更常用的還是 Portal Buffers。
接下來我們在 main 目錄下新建 proto 文件夾,如下:
注意,這個文件夾位置是默認(rèn)的。如果我們的 proto 文件不是放在 src/main/proto 位置,那么在配置插件的時候需要指定 proto 文件的位置,咱們本篇文章主要是入門,我這里就使用默認(rèn)的位置。
在 proto 文件夾中,我們新建一個 product.proto 文件,內(nèi)容如下:
這段配置算是一個比較核心的配置了,這里主要說明了負(fù)責(zé)進程傳輸?shù)念悺⒎椒ǖ鹊降资莻€啥樣子:
- syntax = "proto3";:這個是 protocol buffers 的版本。
- option java_multiple_files = true;:這個字段是可選的,如果設(shè)置為 true,表示每一個 message 文件都會有一個單獨的 class 文件;否則,message 全部定義在 outerclass 文件里。
- option java_package = "org.javaboy.grpc.demo";:這個字段是可選的,用于標(biāo)識生成的 java 文件的 package。如果沒有指定,則使用 proto 里定義的 package,如果package 也沒有指定,那就會生成在根目錄下。
- option java_outer_classname = "ProductProto";:這個字段是可選的,用于指定 proto 文件生成的 java 類的 outerclass 類名。什么是 outerclass?簡單來說就是用一個 class 文件來定義所有的 message 對應(yīng)的 Java 類,這個 class 就是 outerclass;如果沒有指定,默認(rèn)是 proto 文件的駝峰式;
- package product;:這個屬性用來定義 message 的包名。包名的含義與平臺語言無關(guān),這個 package 僅僅被用在 proto 文件中用于區(qū)分同名的 message 類型。可以理解為 message 全名的前綴,和 message 名稱合起來唯一標(biāo)識一個 message 類型。當(dāng)我們在 proto 文件中導(dǎo)入其他 proto 文件的 message,需要加上 package 前綴才行。所以包名是用來唯一標(biāo)識 message 的。
- service:我們定義的跨平臺方法都寫在 service 中,上面的案例中我們定義了兩個方法:addProduct 表示添加一件商品,參數(shù)是一個 Product 對象,返回值則是剛剛添加成功的商品的 ID;getProduct 則表示根據(jù) ID 查詢一個商品,參數(shù)是一個商品 ID,返回值則是查詢到的商品對象。這里的定義相當(dāng)于一個接口,將來我們要在 Java 代碼中實現(xiàn)這個接口。
- message:這里有點像我們在 Java 中定義類,上文中我們定義了兩個類,分別是 Product 和 ProductId 兩個類。這兩個類在 service 中被使用。
message 中定義的有點像我們 Java 中定義的類,但是不能直接使用 Java 中的數(shù)據(jù)類型,畢竟這是 Protocol buffers,這個是和語言無關(guān)的,將來可以據(jù)此生成不同語言的代碼,這里我們可以使用的類型和我們 Java 類型之間的對應(yīng)關(guān)系如下:
另外我們在 message 中定義的屬性的時候,都會給一個數(shù)字,例如 id=1,name=2 等,這個數(shù)字將來會在二進制消息中標(biāo)識我們的字段,并且一旦我們的消息類型被使用就不應(yīng)更改,這個有點像序列化的感覺。
實際上,這個 message 編譯后的字節(jié)內(nèi)容大概像下面這樣:
這里的標(biāo)簽中的內(nèi)容包含兩部分,字段索引和字段類型,字段索引其實就是我們上面定義的數(shù)字。
定義完成之后,接下來我們就需要使用插件來生成對應(yīng)的 Java 代碼了,插件我們在前面已經(jīng)引入了,現(xiàn)在只需要執(zhí)行了,如下圖:
注意,compile 和 compile-custom 兩個指令都需要執(zhí)行。其中 compile 用來編譯消息對象,compile-custom 則依賴消息對象,生成接口服務(wù)。
首先我們點擊 compile 看看生成的代碼,如下:
再看 compile-custom 生成的代碼,如下:
好了,這樣我們的準(zhǔn)備工作就算完成了。
有的小伙伴生成的代碼文件夾顏色不對勁,此時有兩種解決辦法:1.選中目標(biāo)文件夾,右鍵單擊,選擇 Mark Directory as-> Generated Sources root;2.選中工程,右鍵單擊,選擇 Maven->Reload project。推薦使用第二種方案。
3.2 grpc-server
接下來我們創(chuàng)建 grpc-server 項目,并使該項目依賴 grpc-api,然后在 grpc-server 中,提供 ProductInfo 的具體實現(xiàn):
ProductInfoGrpc.ProductInfoImplBase 是根據(jù)我們在 proto 文件中定義的 service 自動生成的,我們的 ProductInfoImpl 繼承自該類,并且提供了我們給出的方法的具體實現(xiàn)。
以 addProduct 方法為例,參數(shù) request 就是將來客戶端調(diào)用的時候傳來的 Product 對象,返回結(jié)果則通過 responseObserver 來完成。我們的方法邏輯很簡單,我就把參數(shù)傳來的 Product 對象打印出來,然后構(gòu)建一個 ProductId 對象并返回,最后調(diào)用 responseObserver.onCompleted(); 表示數(shù)據(jù)返回完畢。
剩下的 getProduct 方法邏輯就很好懂了,我這里就不再贅述了。
最后,我們再把這個 grpc-server 項目啟動起來:
由于我們這里是一個 JavaSE 項目,為了避免項目啟動之后就停止,我們這里調(diào)用了 server.awaitTermination(); 方法,就是讓服務(wù)啟動成功之后不要停止。
3.3 grpc-client
最后再來看看客戶端的調(diào)用。首先 grpc-client 項目也是需要依賴 grpc-api 的,然后直接進行方法調(diào)用,如下:
小伙伴們看到,這里首先需要和服務(wù)端建立連接,給出服務(wù)端的地址和端口號即可,usePlaintext() 方法表示不使用 TLS 對連接進行加密(默認(rèn)情況下會使用 TLS 對連接進行加密),生產(chǎn)環(huán)境建議使用加密連接。
剩下的代碼就比較好懂了,創(chuàng)建 Product 對象,調(diào)用 addProduct 方法進行添加;創(chuàng)建 ProductId 對象,調(diào)用 getProduct。Product 對象和 ProductId 對象都是根據(jù)我們在 proto 中定義的 message 自動生成的。
4. 總結(jié)
好啦,一個簡單的例子,小伙伴們先對 gRPC 入個門,后面松哥會再整幾篇文章跟大家介紹這里邊的一些細(xì)節(jié)。