感謝平臺分享-http://bjbsair.com/2020-04-10/tech-info/53317.html
此時,您應該已經聽說過" gRPC"(標題中至少一次)。 在本文中,我將着重介紹採用gRPC作爲微服務之間的通信介質的好處。
首先,我將嘗試簡要介紹一下架構演變的歷史。 其次,我將重點介紹使用REST(作爲媒介)和可能出現的問題。 第三,gRPC啓動。最後,我將以我的開發工作流程爲例。
架構發展簡史
本節將列出並討論每種體系結構的優缺點(着重於基於Web的應用程序)
整體式
一切都在一個包中。
優點:
· 容易上手
· 單一代碼庫可滿足所有需求
缺點:
· 難以擴展(部分)
· 加載服務器(服務器端渲染)
· 不良的用戶體驗(加載時間長)
· 難以擴展的開發團隊
Monolith architecture
Inside monolith architecture
Monolith v2(前端-後端)
前端邏輯和後端邏輯之間的清晰分隔。 後端仍然龐大。
優點:
· 可以將團隊分爲前端和後端
· 更好的用戶體驗(客戶端的前端邏輯(應用程序))
缺點:
· [仍然]難以擴展(部分)
· [仍然]難以擴展的開發團隊
Frontend-Backend architecture
微服務
每一件事物一項服務(包)。 使用網絡在每個軟件包之間進行通信。
優點:
· 可擴展的組件
· 可擴展團隊
· 靈活的語言選擇(如果使用標準通訊方式)
· 獨立部署/修復每個軟件包
缺點:
· 介紹網絡問題(通信之間的等待時間)
· 服務之間進行通信所需的文檔,協議
· 如果使用共享數據庫,則難以識別錯誤
Micro-service architecture with shared database
Micro-service architecture with standalone database per service
REST(作爲媒介)和可能出現的問題
REST(基於HTTP的JSON)由於易於使用,是當前服務之間通信的最流行方式。 使用REST使您可以靈活地爲每種服務使用任何語言。
Typical REST call
但是,靈活性會帶來一些陷阱。 開發人員之間需要非常嚴格的協議。 下面的草圖展示了一個非常常見的場景,通常在開發過程中發生。
Developer A want Developer B to make a service
Bad request
Expectation vs Actual
問題:
· 依靠人類的同意
· 依賴文檔(需要維護/更新)
· 從協議到協議(這兩種服務)都需要大量的"格式化,解析"
· 大多數開發時間都花在了協議和格式化上,而不是業務邏輯上
gRPC啓動
gRPC是可以在任何環境中運行的現代開源高性能RPC框架。
什麼是RPC? RPC代表遠程過程調用。 它是一種協議,一個程序可用於從網絡上另一臺計算機上的程序請求服務,而無需瞭解網絡的詳細信息。
Remote Procedure Call
以REST爲媒介的RPC
使用服務創建者提供的RPC客戶端/庫將確保調用服務時的正確性。 如果我們要使用RPC和REST作爲媒介,則開發人員B必須編寫客戶端代碼供開發人員A使用。 如果兩個開發人員都使用不同的選擇語言,那麼這對開發人員B來說是一個主要問題,因爲他需要用他不習慣的另一種語言來編寫PRC客戶。 而且,如果不同的服務也需要使用服務B,則開發人員B將不得不花費大量時間來使用不同的語言來製作RPC客戶端,並且必須對其進行維護。
原蟲?
協議緩衝區是Google的語言無關,平臺無關的可擴展機制,用於序列化結構化數據。 gRPC使用protobuf作爲定義數據結構和服務的語言。 您可以將其與REST服務的嚴格文檔進行比較。 Protobuf語法非常嚴格,因此機器可以進行編譯。
下面的代碼塊是一個簡單的原始文件,描述了一個簡單的待辦事項服務以及用於通信的數據結構。
用於定義數據結構的" message"關鍵字
用於定義服務的" service"關鍵字
" rpc"關鍵字,用於定義服務功能
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) {}
}
將原始代碼編譯爲服務器代碼
由於protobuf非常嚴格,因此我們可以使用" protoc"將proto文件編譯爲服務器代碼。 編譯後,您需要對其實施真實的邏輯。
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類型(不過,在本文中我不會強調)
· 一元RPC(請求-響應)
· 客戶端流式RPC
· 服務器流式RPC
· 雙向流式RPC
開發流程
爲了在各個團隊之間採用gRPC,我們需要一些東西。
· 集中式代碼庫(用於服務之間通信的gRPC規範)
· 自動生成代碼
· 服務用戶(客戶)可以通過軟件包管理器使用生成的代碼(用於他們選擇的語言),例如。 去獲取/點安裝
此示例的代碼可以在此倉庫中找到:
代碼庫的結構
.
├── HISTORY.md
├── Makefile
├── README.md
├── genpyinit.sh
├── gogrpcspec //go generated code here
│ └── ...
├── proto
│ └── todo.proto
├── pygrpcspec //python generated code here
│ ├── ...
└── setup.py
git鉤子
我將設置githook,以便在提交之前自動生成內容。 如果合適,您可以使用CI(drone / gitlab / jenkins /…)。 (使用githook的缺點是每個開發人員都需要先配置githook)
您需要一個目錄(文件夾)來保留預提交腳本。 我稱之爲" .githooks"
$ mkdir .githooks
$ cd .githooks/
$ cat <<EOF > pre-commit
#!/bin/sh
set -e
make generate
git add gogrpcspec pygrpcspec
EOF
$ chomd +x pre-commit
預提交腳本將觸發Makefile並git添加2個目錄(gogrpcsepc,pygrpcspec)
爲了使githooks正常工作,開發人員必須運行以下git config命令:
$ git config core.hooksPath .githooks
我們將此命令添加到Makefile中,以使開發人員可以輕鬆地運行此命令(稱爲" make init")。 Makefile的內容應如下所示。
# content of: Makefile
init:
git config core.hooksPath .githooks
generate:
# TO BE CONTINUE
產生程式碼
我們已經設置了githooks來運行Makefile(" make generate")。 讓我們深入瞭解將自動生成代碼的命令。 本文將重點介紹兩種語言-go,python
生成go代碼
我們可以使用protoc將.proto文件編譯成go代碼。
protoc --go_out=plugins=grpc:. ${pwd}/proto/*.proto
\--proto_path=${pwd}
我們將改爲通過docker使用protoc(爲了便於開發人員使用)
docker run --rm -v ${CURDIR}:${CURDIR} -w ${CURDIR} \
znly/protoc \
--go_out=plugins=grpc:. \
${CURDIR}/proto/*.proto \
--proto_path=${CURDIR}
看一下下面的generate命令(我們將刪除,生成並將代碼移動到適當的文件夾中)
# 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
生成代碼後,希望將代碼用於服務器或客戶端的存根以調用服務的用戶(開發人員)可以使用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(爲了便於開發人員使用)
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}
爲了使生成的代碼進入python包以通過pip安裝,我們需要執行額外的步驟:
· 創建setup.py
· 修改生成的代碼(生成的代碼使用文件夾名稱導入,但我們將其更改爲相對名稱)
· 文件夾需要包含" init.py",以暴露生成的代碼
使用以下模板創建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 + '\n\n' + 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)
產生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',
]
爲了使開發人員能夠添加更多.proto文件並自動生成init.py,一個簡單的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模塊,則可以跳過此閱讀)
我們希望將每個"從原始導入"更改爲"從"。 進口"。 這背後的原因是因爲我們將數據類型,服務存根都放在同一目錄中,並且爲了在模塊外部調用模塊,每個內部引用都應該是相對的。
sed -i -E 's/^from proto import/from . import/g' *.py
此時,您的Makefile應該如下所示:
# 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"
生成代碼後,希望將代碼用於服務器或客戶端的存根以調用服務的用戶(開發人員)可以使用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的語法嚴格性可以將gRPC編譯成多種不同語言的客戶端代碼,因此gRPC是在微服務之間進行通信的一種絕佳方式。
All codes in this article: