gRPC怎样节省您的开发时间
此时,您应该已经听说过" 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=‘‘, 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: