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