現(xiàn)如今微服務(wù)很流行撇寞,而微服務(wù)很有可能是使用不同語言進(jìn)行構(gòu)建的应结。而微服務(wù)之間通常需要相互通信,所以微服務(wù)之間必須在以下幾個(gè)方面達(dá)成共識(shí):
需要使用某種API
數(shù)據(jù)格式
錯(cuò)誤的模式
負(fù)載均衡
奕枢。信轿。。
現(xiàn)在最流行的一種API風(fēng)格可能是REST曹铃,它主要是通過HTTP協(xié)議來傳輸JSON數(shù)據(jù)缰趋。
但是現(xiàn)在我們可以看看gRPC(https://grpc.io/),它來自Google陕见,并且支持眾多主流的語言包括Go秘血,Dart,C#评甜,C/C++直撤,Nodejs,Python等等蜕着。
下面就學(xué)習(xí)一下gRPC。
gRPC能解決哪些問題红柱?
構(gòu)建(Web)API是挺麻煩的承匣,因?yàn)闃?gòu)建API時(shí)我們得考慮:
數(shù)據(jù)的格式是JSON、XML還是二進(jìn)制的锤悄;
端點(diǎn)地址以及GET還是POST等韧骗;
如何調(diào)用API以及對(duì)異常的處理規(guī)則;
API的效率:一次調(diào)用讀取多少數(shù)據(jù)零聚?是否太多了或太少了袍暴?太少的話可能會(huì)導(dǎo)致多次API的調(diào)用;
延遲隶症;
擴(kuò)展性政模,是否能支持成上千個(gè)客戶端
負(fù)載均衡
與其他語言的互操作性
如何處理身份認(rèn)證、監(jiān)控蚂会、日志等等
以上這些問題據(jù)說gRPC都能解決淋样。。??
再次介紹一下gRPC
之前說了gRPC來自Google胁住,它是一個(gè)開源的框架趁猴;它同時(shí)也是Cloud Native Computation基金會(huì)(CNCF)的一部分,就像Docker和Kubernetes一樣彪见。
gRPC允許你為RPC(Remote Procedure Call)定義請(qǐng)求和響應(yīng)儡司,然后gRPC會(huì)幫你處理一切剩余問題。
它速度快余指,執(zhí)行效率高捕犬,基于HTTP/2構(gòu)建,低延遲,支持流或听,與開發(fā)語言無關(guān)探孝,并且可以很簡(jiǎn)單的插入身份認(rèn)證、負(fù)載均衡誉裆、日志和監(jiān)控等功能顿颅。
RPC是啥
RPC是(Remote Procedure Call)遠(yuǎn)程過程調(diào)用。
在客戶端代碼使用RPC調(diào)用的時(shí)候足丢,就像直接調(diào)用了服務(wù)端的一個(gè)函數(shù)一樣粱腻。
例如在服務(wù)器端代碼是這樣的:
而在“遙遠(yuǎn)”的客戶端它是這樣調(diào)用服務(wù)器端的邏輯的,就像調(diào)用本地方法一樣:
而實(shí)際上客戶端在調(diào)用這個(gè)方法的時(shí)候斩跌,是要走網(wǎng)絡(luò)通信的绍些。
RPC它不是一個(gè)新的概念,很早它就出現(xiàn)了耀鸦。但是它存在很多的問題柬批。而gRPC它是對(duì)RPC一種非常簡(jiǎn)潔的實(shí)現(xiàn)并且解決了很多RPC的問題。
如何學(xué)習(xí)gRPC袖订?
首先氮帐,你得學(xué)習(xí)Protocol Buffers(https://developers.google.com/protocol-buffers/),簡(jiǎn)單的說洛姑,它可以用來定義消息和服務(wù)上沐。
然后,你只需要實(shí)現(xiàn)服務(wù)即可楞艾,剩余的gRPC代碼將會(huì)自動(dòng)為你生成参咙。
.proto這個(gè)文件可以適用于十幾種開發(fā)語言(包括服務(wù)端和客戶端),并且它允許你使用同一個(gè)框架來支持每秒百萬級(jí)以上的RPC調(diào)用硫眯。
gPRC使用的是合約優(yōu)先的API開發(fā)模式蕴侧,它默認(rèn)使用Protocol buffers (protobuf) 作為接口設(shè)計(jì)語言(IDL),這個(gè).proto文件包括兩部分:
gRPC服務(wù)的定義
服務(wù)端和客戶端之間傳遞的消息
看一個(gè)官網(wǎng)的例子(protobuf):
在這里定義了一個(gè)Greeter服務(wù)两入,它里面定義了一個(gè)SayHello的rpc調(diào)用戈盈。SayHello會(huì)發(fā)送HelloRequest這個(gè)消息,接收HelloReply這個(gè)消息谆刨。
為什么使用Protocol Buffers塘娶?
因?yàn)椋?/p>
它和開發(fā)語言無關(guān)
可以生成所有主流開發(fā)語言的代碼
數(shù)據(jù)是二進(jìn)制格式的,串行化的效率高痊夭,Payload比較小
也很適合傳遞大量的數(shù)據(jù)
通過設(shè)定某些規(guī)則刁岸,是的API的進(jìn)化也很簡(jiǎn)單
Protocol Buffer
開發(fā)環(huán)境:
IDE: VSCode
VSCode的擴(kuò)展插件:vscode-proto3和Clang-Format這兩個(gè)擴(kuò)展
Windows還需要安裝Clang,Windows 64位系統(tǒng)的地址如下:Clang for Windows (64-bit)她我;Mac:brew install clang-format虹曙。
第一個(gè)例子
選個(gè)文件夾迫横,建立一個(gè)名叫first.proto的文件:
1. 這行代碼表示我們使用的是語法是proto3,之前還有一個(gè)proto2酝碳;如果你不寫這一行矾踱,那么protocol buffer編譯器會(huì)認(rèn)為你采用的是proto2。這個(gè)必須是文件的第一個(gè)非空非注釋行疏哗。
2. 這里是定義了一個(gè)消息名稱為FirstMessage呛讲,類型是message。它里面定義了三個(gè)字段返奉,它們都是標(biāo)量類型(Scalar Type)贝搁,你也可以定義復(fù)合類型,這個(gè)以后再說芽偏。
3. 是指字段(Field)的類型
4. 字段的名稱
5. 字段的數(shù)值(也叫Tag)雷逆,這個(gè)數(shù)字是唯一的。它們是用來在信息格式里識(shí)別你的字段的污尉,一旦該類型被使用了膀哲,那么這個(gè)數(shù)字就不要再改變了。
標(biāo)量類型
數(shù)值型
數(shù)值型有很多種形式:double, float, int32, int64, uint32, uint64, sint32, sint64, fixed32, fixed64, sfixed32, sfixed64被碗。
根據(jù)需要選擇對(duì)應(yīng)的數(shù)值類型某宪。
布爾型
bool型可以有True和False兩個(gè)值。
字符串
string表示任意長(zhǎng)度的文本蛮放,但是它必須包含的是UTF-8編碼或7位ASCII的文本,長(zhǎng)度不可超過232奠宜。
字節(jié)型
bytes可表示任意的byte數(shù)組序列包颁,但是長(zhǎng)度也不可以超過232?,最后是由你來決定如何解釋這些bytes压真。例如你可以使用這個(gè)類型來表示一個(gè)圖片娩嚼。
做個(gè)例子
可以自己做一個(gè)例子,需求是這樣的:這個(gè)信息表示的是一個(gè)人Person滴肿,使用proto3語法岳悟,字段如下:ID,姓名泼差,身高贵少,體重,頭像堆缘,電子郵件滔灶,郵件是否已驗(yàn)證。
這個(gè)應(yīng)該沒有什么難度吼肥,不過要注意一下別忘記標(biāo)點(diǎn)符號(hào)录平。
字段的數(shù)值(Tag)
在Protocol Buffers里面麻车,字段的名其實(shí)沒那么重要,但是寫C#代碼的時(shí)候斗这,字段名還是很重要的动猬。
對(duì)于protobuf來說,這個(gè)tag是更為重要的表箭。
可以使用的最小的tag數(shù)值是1赁咙,最大值是229- 1, 或者 536,870,911。但是你不可以使用19000到19999之間的數(shù)燃逻,這部分?jǐn)?shù)是保留的序目。
還有一點(diǎn)值得注意的是:
從1到15的Tag數(shù)只占用1個(gè)字節(jié)的空間,所以它們應(yīng)該被用在頻繁使用的字段上伯襟。而從16到2047猿涨,則占用兩個(gè)字節(jié),它們可以用在不頻繁使用的字段上姆怪。
字段規(guī)則
protobuf的字段必須滿足以下兩個(gè)規(guī)則之一:
單數(shù)字段(Singular)
大概意思就是指這個(gè)字段只能出現(xiàn)0或1次(不能超過一次)叛赚,這也是proto3的默認(rèn)字段規(guī)則。
重復(fù)字段(Repeated)
與singular相對(duì)的就是repeated稽揭。如果你想做一個(gè)list或數(shù)組的話俺附,你可以使用重復(fù)字段這個(gè)概念。這個(gè)list可以有任何數(shù)量(包括0)的元素溪掀。它里面的值的順序?qū)?huì)得到保留事镣。
Repeated Fields 例子
還是使用前面的Person這個(gè)例子,我們?cè)诶锩嫣砑右粋€(gè)repeated字段(電話號(hào)碼):
就是在前面加上repeated這個(gè)關(guān)鍵字即可揪胃。
在proto3里面璃哟,標(biāo)量類型的repeated字段采用的是packed編碼。
注釋
proto文件里可以添加注釋喊递。它們通常被當(dāng)作你定義的這些消息的文檔随闪。
注釋很簡(jiǎn)單,還是兩種形式骚勘,直接看代碼就明白了:
保留的字段
如果你對(duì)你定義的消息類型進(jìn)行了更新铐伴,例如刪除某個(gè)字段或者注釋掉某個(gè)字段,那么其它開發(fā)者在以后更新這個(gè)消息類型的時(shí)候可能會(huì)重新使用被你刪除/注釋掉的字段的數(shù)值(tag)俏讹。如果以后還需要使用這個(gè)消息類型的老版本的proto文件当宴,那么這將會(huì)引起嚴(yán)重的問題,例如數(shù)據(jù)損壞泽疆、隱私漏洞等等即供。
那么一種避免此類事情發(fā)生的解決辦法就是將你刪除/注釋掉的這些字段的數(shù)值(或/并且包括字段名,因?yàn)樽侄蚊梢餔SON序列化的問題)標(biāo)記為reserved于微,如果其他人再使用這個(gè)數(shù)值作為字段標(biāo)識(shí)符逗嫡,那么編譯器就會(huì)有錯(cuò)誤提示:
注意青自,不可以把reserved數(shù)值和字段名放在同一個(gè)reserved語句里。
字段的默認(rèn)值
當(dāng)消息被解析的時(shí)候驱证,如果編碼的消息里不含有特定的一個(gè)singular元素延窜,那么在被解析對(duì)象里相應(yīng)的字段就會(huì)被設(shè)為默認(rèn)值。
常用類型的默認(rèn)值如下:
string:空字符串
bytes:空的byte數(shù)組
bool:false
數(shù)值型:0
枚舉enum:枚舉里定義的第一個(gè)枚舉值抹锄,值必須是0
repeated:通常是相應(yīng)開發(fā)語言里的空list
還有個(gè)消息類型的字段逆瑞,它的默認(rèn)值和開發(fā)語言有關(guān),這個(gè)以后再說伙单。
枚舉
之前說了获高,枚舉里面定義的第一個(gè)值就是這個(gè)枚舉的默認(rèn)值。
Enum的tag必須從0開始吻育,所以0就是枚舉的數(shù)值默認(rèn)值念秧。
繼續(xù)上個(gè)例子
我們對(duì)Person添加一個(gè)枚舉類型的字段:性別 Gender:
首先需要定義枚舉類型,這里定義了一個(gè)枚舉布疼,名稱是Gender摊趾,里面有3個(gè)值,默認(rèn)值是NOT_SPECIFIED游两,數(shù)值默認(rèn)值就是0砾层。
然后使用這個(gè)枚舉類型定義了一個(gè)字段,名稱為gender贱案,tag數(shù)為10肛炮。
為枚舉值起別名
枚舉值是可以起別名的,起別名的作用就是允許兩個(gè)枚舉值擁有同一個(gè)數(shù)值宝踪。
要想起別名侨糟,首先需要設(shè)置allow_alias這個(gè)option為true:
然后我們?yōu)镕EMALE這個(gè)枚舉值起了一個(gè)別名叫做WOMAN,它們的數(shù)值是一樣的肴沫。同樣的MAN是MALE的數(shù)值也是一樣的粟害。
枚舉里面的常量的值必須不能超過32位整型的數(shù)值蕴忆,不建議使用負(fù)數(shù)颤芬。
枚舉可以定義在message里面,也可以在外邊單獨(dú)定義以便復(fù)用套鹅。如果另一個(gè)消息想使用Person里面這個(gè)Gender枚舉站蝠,那么可以使用Person.Gender這種形式。
針對(duì)枚舉值被刪除/注釋掉這種情況卓鹿,它也可以使用reserved:
數(shù)值和常量名也必須分開使用兩個(gè)reserved語句菱魔。
其中max表示可能的最大的值。
使用其它的信息類型
可以使用其它的信息類型作為字段的類型吟孙。
我們可以在同一個(gè)proto文件里定義多個(gè)信息類型(為了截圖方便澜倦,我去掉了Person的一些字段):
在這個(gè)文件里聚蝶,除了Person信息類型外,我還定義了Date信息類型藻治。
所以碘勉,我可以在Person里面使用Date作為它的字段類型:
引入定義
如果想要使用的信息類型已經(jīng)在其它的proto文件定義好了呢?這個(gè)時(shí)候就需要引入信息類型的定義桩卵。
現(xiàn)在我把Date定義移動(dòng)到了date.proto這個(gè)文件里面:
然后在person.proto里面我們可以引用date.proto:
嵌套類型
Protocol Buffer允許在信息類型里面定義其它的信息類型验靡。
直接看例子:
如果想在Person外邊使用Address這個(gè)類型,那么就需要這樣用:Person.Address雏节。
打包
你可以向proto文件添加可選的打包(package)說明符胜嗓,以避免消息類型間的名稱沖突。
所以說打包是很必要的钩乍。
打包之后生成的C#代碼就會(huì)使用命名空間來對(duì)應(yīng)proto里面的package辞州,但是命名方式會(huì)改為Pascal Case(每個(gè)單詞首字母大寫)。
上面的代碼在C#里面的情況就是:Person類在My.Project這個(gè)命名空間下件蚕。
但是如果你在proto文件里設(shè)置了option csharp_namespace這個(gè)選項(xiàng)孙技,那么在C#里的命名空間就是該選項(xiàng)指定的命名空間了:
這時(shí)候,C#里面Perosn類的命名空間就是My.WebApis了排作,但是在proto文件里它的包還是my.project牵啦。
設(shè)置Protocol Buffers編譯器
protoc編譯器主要就是用來生成代碼的,它的下載地址目前是:https://github.com/protocolbuffers/protobuf/releases/
在里面選擇你使用的操作系統(tǒng)的版本:
下載后解壓縮到某個(gè)路徑妄痪,然后把解壓目錄下的bin目錄添加到系統(tǒng)的環(huán)境變量里哈雏。
然后打開命令行,輸入protoc衫生,如果有類似下面的東西出現(xiàn)裳瘪,說明安裝成功了:
這里面的--proto_path=PATH這個(gè)參數(shù)比較常用,它用來指定到哪個(gè)文件見來查找引入罪针。
再有就這個(gè)參數(shù)很常用:
--csharp_out=OUT_DIR用來指定存放生成的C#代碼的目錄彭羹。
我們先試驗(yàn)一下,生成Person的C#代碼:
執(zhí)行成功后就沒有任何提示泪酱,打開csharp目錄派殷,可以看到Person.cs這個(gè)文件:
而Person.cs文件里面的代碼就比較多了:
千萬不要去修改這個(gè)文件!