此時(shí),您應(yīng)該已經(jīng)聽說過" gRPC"(標(biāo)題中至少一次)。 在本文中,我將著重介紹采用gRPC作為微服務(wù)之間的通信介質(zhì)的好處。
首先,我將嘗試簡要介紹一下架構(gòu)演變的歷史。 其次,我將重點(diǎn)介紹使用REST(作為媒介)和可能出現(xiàn)的問題。 第三,gRPC啟動。最后,我將以我的開發(fā)工作流程為例。
架構(gòu)發(fā)展簡史
本節(jié)將列出并討論每種體系結(jié)構(gòu)的優(yōu)缺點(diǎn)(著重于基于Web的應(yīng)用程序)
整體式
一切都在一個(gè)包中。
優(yōu)點(diǎn):
· 容易上手
· 單一代碼庫可滿足所有需求
缺點(diǎn):
· 難以擴(kuò)展(部分)
· 加載服務(wù)器(服務(wù)器端渲染)
· 不良的用戶體驗(yàn)(加載時(shí)間長)
· 難以擴(kuò)展的開發(fā)團(tuán)隊(duì)

Monolith architecture

Inside monolith architecture
Monolith v2(前端-后端)
前端邏輯和后端邏輯之間的清晰分隔。 后端仍然龐大。
優(yōu)點(diǎn):
· 可以將團(tuán)隊(duì)分為前端和后端
· 更好的用戶體驗(yàn)(客戶端的前端邏輯(應(yīng)用程序))
缺點(diǎn):
· [仍然]難以擴(kuò)展(部分)
· [仍然]難以擴(kuò)展的開發(fā)團(tuán)隊(duì)

Frontend-Backend architecture
微服務(wù)
每一件事物一項(xiàng)服務(wù)(包)。 使用網(wǎng)絡(luò)在每個(gè)軟件包之間進(jìn)行通信。
優(yōu)點(diǎn):
· 可擴(kuò)展的組件
· 可擴(kuò)展團(tuán)隊(duì)
· 靈活的語言選擇(如果使用標(biāo)準(zhǔn)通訊方式)
· 獨(dú)立部署/修復(fù)每個(gè)軟件包
缺點(diǎn):
· 介紹網(wǎng)絡(luò)問題(通信之間的等待時(shí)間)
· 服務(wù)之間進(jìn)行通信所需的文檔,協(xié)議
· 如果使用共享數(shù)據(jù)庫,則難以識別錯(cuò)誤

Micro-service architecture with shared database

Micro-service architecture with standalone database per service
REST(作為媒介)和可能出現(xiàn)的問題
REST(基于HTTP的JSON)由于易于使用,是當(dāng)前服務(wù)之間通信的最流行方式。 使用REST使您可以靈活地為每種服務(wù)使用任何語言。

Typical REST call
但是,靈活性會帶來一些陷阱。 開發(fā)人員之間需要非常嚴(yán)格的協(xié)議。 下面的草圖展示了一個(gè)非常常見的場景,通常在開發(fā)過程中發(fā)生。

Developer A want Developer B to make a service

Bad request

Expectation vs Actual
問題:
· 依靠人類的同意
· 依賴文檔(需要維護(hù)/更新)
· 從協(xié)議到協(xié)議(這兩種服務(wù))都需要大量的"格式化,解析"
· 大多數(shù)開發(fā)時(shí)間都花在了協(xié)議和格式化上,而不是業(yè)務(wù)邏輯上
gRPC啟動
gRPC是可以在任何環(huán)境中運(yùn)行的現(xiàn)代開源高性能RPC框架。
什么是RPC? RPC代表遠(yuǎn)程過程調(diào)用。 它是一種協(xié)議,一個(gè)程序可用于從網(wǎng)絡(luò)上另一臺計(jì)算機(jī)上的程序請求服務(wù),而無需了解網(wǎng)絡(luò)的詳細(xì)信息。

Remote Procedure Call
以REST為媒介的RPC
使用服務(wù)創(chuàng)建者提供的RPC客戶端/庫將確保調(diào)用服務(wù)時(shí)的正確性。 如果我們要使用RPC和REST作為媒介,則開發(fā)人員B必須編寫客戶端代碼供開發(fā)人員A使用。 如果兩個(gè)開發(fā)人員都使用不同的選擇語言,那么這對開發(fā)人員B來說是一個(gè)主要問題,因?yàn)樗枰盟涣?xí)慣的另一種語言來編寫PRC客戶。 而且,如果不同的服務(wù)也需要使用服務(wù)B,則開發(fā)人員B將不得不花費(fèi)大量時(shí)間來使用不同的語言來制作RPC客戶端,并且必須對其進(jìn)行維護(hù)。
原蟲?
協(xié)議緩沖區(qū)是google的語言無關(guān),平臺無關(guān)的可擴(kuò)展機(jī)制,用于序列化結(jié)構(gòu)化數(shù)據(jù)。 gRPC使用protobuf作為定義數(shù)據(jù)結(jié)構(gòu)和服務(wù)的語言。 您可以將其與REST服務(wù)的嚴(yán)格文檔進(jìn)行比較。 Protobuf語法非常嚴(yán)格,因此機(jī)器可以進(jìn)行編譯。
下面的代碼塊是一個(gè)簡單的原始文件,描述了一個(gè)簡單的待辦事項(xiàng)服務(wù)以及用于通信的數(shù)據(jù)結(jié)構(gòu)。
用于定義數(shù)據(jù)結(jié)構(gòu)的" message"關(guān)鍵字
用于定義服務(wù)的" service"關(guān)鍵字
" rpc"關(guān)鍵字,用于定義服務(wù)功能
syntax = "proto3";
package gogrpcspec;
message Employee {
string name = 1;
}
message Task {
Employee employee = 1;
string name = 2;
string status = 3;
}
message Summary {
int32 todoTasks = 1;
int32 doingTasks = 2;
int32 doneTasks = 3;
}
message SpecificSummary {
Employee employee = 1;
Summary summary = 2;
}
service TaskManager {
rpc GetSummary(Employee) returns (SpecificSummary) {}
rpc AddTask(Task) returns (SpecificSummary) {}
rpc AddTasks(stream Task) returns(Summary) {}
rpc GetTasks(Employee) returns (stream Task) {}
rpc ChangeToDone(stream Task) returns (stream Task) {}
}
將原始代碼編譯為服務(wù)器代碼
由于protobuf非常嚴(yán)格,因此我們可以使用" protoc"將proto文件編譯為服務(wù)器代碼。 編譯后,您需要對其實(shí)施真實(shí)的邏輯。
protoc --go_out=plugins=grpc:. ${pwd}/proto/*.proto
--proto_path=${pwd}
編譯原始代碼到客戶端代碼
有了proto文件,我們可以使用" protoc"將其客戶端代碼編譯為許多流行的語言:C#,C ++,Dart,Go,JAVA,JavaScript,Objective-C,php,Python,Ruby等。
gRPC rpc類型
gRPC支持多種rpc類型(不過,在本文中我不會強(qiáng)調(diào))
· 一元RPC(請求-響應(yīng))
· 客戶端流式RPC
· 服務(wù)器流式RPC
· 雙向流式RPC
開發(fā)流程
為了在各個(gè)團(tuán)隊(duì)之間采用gRPC,我們需要一些東西。
· 集中式代碼庫(用于服務(wù)之間通信的gRPC規(guī)范)
· 自動生成代碼
· 服務(wù)用戶(客戶)可以通過軟件包管理器使用生成的代碼(用于他們選擇的語言),例如。 去獲取/點(diǎn)安裝
此示例的代碼可以在此倉庫中找到:
- https://github.com/redcranetech/grpcspec-example
- https://github.com/redcranetech/grpc-go-example
- https://github.com/redcranetech/grpc-python-example
代碼庫的結(jié)構(gòu)
.
├── HISTORY.md
├── Makefile
├── README.md
├── genpyinit.sh
├── gogrpcspec //go generated code here
│ └── ...
├── proto
│ └── todo.proto
├── pygrpcspec //python generated code here
│ ├── ...
└── setup.py
git鉤子
我將設(shè)置githook,以便在提交之前自動生成內(nèi)容。 如果合適,您可以使用CI(drone / gitlab / jenkins /…)。 (使用githook的缺點(diǎn)是每個(gè)開發(fā)人員都需要先配置githook)
您需要一個(gè)目錄(文件夾)來保留預(yù)提交腳本。 我稱之為" .githooks"
$ mkdir .githooks
$ cd .githooks/
$ cat <<EOF > pre-commit
#!/bin/sh
set -e
make generate
git add gogrpcspec pygrpcspec
EOF
$ chomd +x pre-commit
預(yù)提交腳本將觸發(fā)Makefile并git添加2個(gè)目錄(gogrpcsepc,pygrpcspec)
為了使githooks正常工作,開發(fā)人員必須運(yùn)行以下git config命令:
$ git config core.hooksPath .githooks
我們將此命令添加到Makefile中,以使開發(fā)人員可以輕松地運(yùn)行此命令(稱為" make init")。 Makefile的內(nèi)容應(yīng)如下所示。
# content of: Makefile
init:
git config core.hooksPath .githooks
generate:
# TO BE CONTINUE
產(chǎn)生程式碼
我們已經(jīng)設(shè)置了githooks來運(yùn)行Makefile(" make generate")。 讓我們深入了解將自動生成代碼的命令。 本文將重點(diǎn)介紹兩種語言-go,python
生成go代碼
我們可以使用protoc將.proto文件編譯成go代碼。
protoc --go_out=plugins=grpc:. ${pwd}/proto/*.proto
--proto_path=${pwd}
我們將改為通過Docker使用protoc(為了便于開發(fā)人員使用)
docker run --rm -v ${CURDIR}:${CURDIR} -w ${CURDIR}
znly/protoc
--go_out=plugins=grpc:.
${CURDIR}/proto/*.proto
--proto_path=${CURDIR}
看一下下面的generate命令(我們將刪除,生成并將代碼移動到適當(dāng)?shù)奈募A中)
# content of: Makefile
init:
git config core.hooksPath .githooks
generate:
# remove previously generated code
rm -rf gogrpcspec/*
# generate go code
docker run --rm -v ${CURDIR}:${CURDIR} -w ${CURDIR}
znly/protoc
--go_out=plugins=grpc:.
${CURDIR}/proto/*.proto
--proto_path=${CURDIR}
# move generated code into gogrpcspec folder
mv proto/*.go gogrpcspec
生成代碼后,希望將代碼用于服務(wù)器或客戶端的存根以調(diào)用服務(wù)的用戶(開發(fā)人員)可以使用go get命令下載
go get -u github.com/redcranetech/grpcspec-example
然后用
import pb "github.com/redcranetech/grpcspec-example/gogrpcspec"
生成python代碼
我們可以使用protoc將.proto文件編譯成python代碼。
protoc --plugin=protoc-gen-grpc=/usr/bin/grpc_python_plugin
--python_out=./pygrpcspec
--grpc_out=./pygrpcspec
${pwd}/proto/*.proto
--proto_path=${pwd}
我們將改為通過docker使用protoc(為了便于開發(fā)人員使用)
docker run --rm -v ${CURDIR}:${CURDIR} -w ${CURDIR}
znly/protoc --plugin=protoc-gen-grpc=/usr/bin/grpc_python_plugin
--python_out=./pygrpcspec
--grpc_out=./pygrpcspec
${CURDIR}/proto/*.proto
--proto_path=${CURDIR}
為了使生成的代碼進(jìn)入python包以通過pip安裝,我們需要執(zhí)行額外的步驟:
· 創(chuàng)建setup.py
· 修改生成的代碼(生成的代碼使用文件夾名稱導(dǎo)入,但我們將其更改為相對名稱)
· 文件夾需要包含" init.py",以暴露生成的代碼
使用以下模板創(chuàng)建setup.py文件:
# content of: setup.py
from setuptools import setup, find_packages
with open('README.md') as readme_file:
README = readme_file.read()
with open('HISTORY.md') as history_file:
HISTORY = history_file.read()
setup_args = dict(
name='pygrpcspec',
version='0.0.1',
description='grpc spec',
long_description_content_type="text/markdown",
long_description=README + 'nn' + HISTORY,
license='MIT',
packages=['pygrpcspec','pygrpcspec.proto'],
author='Napon Mekavuthikul',
author_email='[email protected]',
keywords=['grpc'],
url='https://github.com/redcranetech/grpcspec-example',
download_url=''
)
install_requires = [
'grpcio>=1.21.0',
'grpcio-tools>=1.21.0',
'protobuf>=3.8.0'
]
if __name__ == '__main__':
setup(**setup_args, install_requires=install_requires)
產(chǎn)生__init__.py
pygrpcspec文件夾的__init__.py必須是
# content of: pygrpspec/__init__.py
from . import proto
__all__ = [
'proto'
]
并且pygrpcspec / proto文件夾的__init__.py必須是
# content of: pygrpspec/proto/__init__.py
from . import todo_pb2
from . import todo_pb2_grpc
__all__ = [
'todo_pb2',
'todo_pb2_grpc',
]
為了使開發(fā)人員能夠添加更多.proto文件并自動生成__init__.py,一個(gè)簡單的shell腳本可以解決此問題
# content of: genpyinit.sh
cat <<EOF >pygrpcspec/__init__.py
from . import proto
__all__ = [
'proto'
]
EOF
pyfiles=($(ls pygrpcspec/proto | sed -e 's/..*$//'| grep -v __init__))
rm -f pygrpcspec/proto/__init__.py
for i in "${pyfiles[@]}"
do
echo "from . import $i" >> pygrpcspec/proto/__init__.py
done
echo "__all__ = [" >> pygrpcspec/proto/__init__.py
for i in "${pyfiles[@]}"
do
echo " '$i'," >> pygrpcspec/proto/__init__.py
done
echo "]" >> pygrpcspec/proto/__init__.py
修改生成的代碼
(如果您不太熟悉python模塊,則可以跳過此閱讀)
我們希望將每個(gè)"從原始導(dǎo)入"更改為"從"。 進(jìn)口"。 這背后的原因是因?yàn)槲覀儗?shù)據(jù)類型,服務(wù)存根都放在同一目錄中,并且為了在模塊外部調(diào)用模塊,每個(gè)內(nèi)部引用都應(yīng)該是相對的。
sed -i -E 's/^from proto import/from . import/g' *.py
此時(shí),您的Makefile應(yīng)該如下所示:
# content of: Makefile
init:
git config core.hooksPath .githooks
generate:
# remove previously generated code
rm -rf gogrpcspec/*
# generate go code
docker run --rm -v ${CURDIR}:${CURDIR} -w ${CURDIR}
znly/protoc
--go_out=plugins=grpc:.
${CURDIR}/proto/*.proto
--proto_path=${CURDIR}
# move generated code into gogrpcspec folder
mv proto/*.go gogrpcspec
# remove previously generated code
rm -rf pygrpcspec/*
# generate python code
docker run --rm -v ${CURDIR}:${CURDIR} -w ${CURDIR}
znly/protoc
--plugin=protoc-gen-grpc=/usr/bin/grpc_python_plugin
--python_out=./pygrpcspec
--grpc_out=./pygrpcspec
${CURDIR}/proto/*.proto
--proto_path=${CURDIR}
# generate __init__.py
sh genpyinit.sh
# modify import using sed
docker run --rm -v ${CURDIR}:${CURDIR} -w ${CURDIR}/pygrpcspec/proto
frolvlad/alpine-bash
bash -c "sed -i -E 's/^from proto import/from . import/g' *.py"
生成代碼后,希望將代碼用于服務(wù)器或客戶端的存根以調(diào)用服務(wù)的用戶(開發(fā)人員)可以使用pip命令下載
pip install -e git+https://github.com/redcranetech/grpcspec-example.git#egg=pygrpcspec
然后用
from pygrpcspec.proto import todo_pb2_grpc
from pygrpcspec.proto import todo_pb2
綜上所述,由于protobuf的語法嚴(yán)格性可以將gRPC編譯成多種不同語言的客戶端代碼,因此gRPC是在微服務(wù)之間進(jìn)行通信的一種絕佳方式。
All codes in this article:
- https://github.com/redcranetech/grpcspec-example
- https://github.com/redcranetech/grpc-go-example
- https://github.com/redcranetech/grpc-python-example
(本文翻譯自Napon Mekavuthikul的文章《gRPC and why it can save you development time》,參考:https://medium.com/red-crane/grpc-and-why-it-can-save-you-development-time-436168fd0cbc)