当我们与微服务一起工作时,通常我们需要以某种方式在它们之间进行通信。基本上,我们有两种选择。同步(API)和异步通信(消息队列)。REST APIs是一种相当直接的方式来创建一个通信渠道。我们有很多框架和微框架来创建REST APIs。例如,在Python中,我们可以使用Flask。REST很简单,它可以适用于很多情况,但有时是不够的。REST API是一种HTTP服务,HTTP是建立在TCP之上的协议。当我们创建一个REST连接时,我们要打开一个TCP连接到服务器,我们发送请求有效载荷,我们接收响应,然后关闭连接。如果我们需要执行大量的连接,也许我们会面临一个瓶颈。此外,我们还有有效载荷。我们需要定义我们要如何对信息进行编码。我们通常使用JSON(我们也可以使用XML)。用几乎所有的语言对JSON进行编码/解码很容易,但JSON是纯文本。通过TCP连接的大的有效载荷意味着缓慢的响应时间。
为了解决这种情况,我们的工具箱中还有一个工具。这个工具就是gRPC。通过gRPC,我们在客户端和服务器之间建立了一个持久的连接(而不是像REST那样打开和关闭连接),同时我们使用二进制的有效载荷来减少大小,提高性能。
首先,我们需要定义我们要使用的协议。这是我们在HTTP APIs中不需要做的事情(我们使用JSON,我们忘记了其他的)。这是一个额外的步骤。并不复杂,但也是一个额外的步骤。我们需要用一个proto文件来定义我们的服务和变量的类型。
// api.proto
syntax = "proto3";
package api;
service Api {
rpc sayHello (HelloRequest) returns (Hello) {}
rpc getAll (ApiRequest) returns (api.Items) {}
rpc getStream (ApiRequest) returns (stream api.Item) {}
}
message ApiRequest {
int32 length = 1;
}
message Items {
repeated api.Item items = 1;
}
message Item {
int32 id = 1;
string name = 2;
}
message HelloRequest {
string name = 1;
}
message Hello {
string message = 1;
}
通过我们的proto文件(与语言无关),我们可以使用我们的编程语言创建我们的服务的封装器。在我的例子中是python。
python -m grpc_tools.protoc -I./protos --python_out=. --grpc_python_out=. ./protos/api.proto
当然,我们可以用一种语言创建客户端,用另一种语言创建服务器。两者都使用同一个proto文件。
它创建了两个文件。我们不需要打开这些文件。我们将导入这些文件来创建我们的客户端和服务器。我们可以直接使用那些文件,但我更喜欢使用一个额外的包装器。不需要重新发明轮子,只是让我容易使用客户端/和服务器。
import grpc
from api_pb2 import Items, Item, Hello, HelloRequest, ApiRequest
from api_pb2_grpc import ApiServicer, ApiStub
class ApiServer(ApiServicer):
def getAll(self, request, context):
data = []
for i in range(1, request.length + 1):
data.append(Item(id=i, name=f'name {i}'))
return Items(items=data)
def getStream(self, request, context):
for i in range(1, request.length + 1):
yield Item(id=i, name=f'name {i}')
def sayHello(self, request, context):
return Hello(message=f'Hello {request.name}!')
class ApiClient:
def __init__(self, target):
channel = grpc.insecure_channel(target)
self.client = ApiStub(channel)
def sayHello(self, name):
response = self.client.sayHello(HelloRequest(name=name))
return response.message
def getAll(self, length):
response = self.client.getAll(ApiRequest(length=length))
return response.items
def getStream(self, length):
response = self.client.getStream(ApiRequest(length=length))
return response
现在我可以创建一个服务器。
import logging
from concurrent import futures
import grpc
import settings
from api import ApiServer
from api_pb2_grpc import add_ApiServicer_to_server
def serve():
server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
add_ApiServicer_to_server(ApiServer(), server)
server.add_insecure_port(f'[::]:{settings.BACKEND_PORT}')
server.start()
server.wait_for_termination()
if __name__ == '__main__':
logging.basicConfig()
serve()
还有一个客户端。在我的例子中,我将使用一个消耗gRPC服务器的flask前端。
from flask import Flask, render_template
import settings
from api import ApiClient
app = Flask(__name__)
app.config["api"] = ApiClient(f"{settings.BACKEND_HOST}:{settings.BACKEND_PORT}")
@app.route("/")
def home():
api = app.config["api"]
return render_template(
"index.html",
name=api.sayHello("Gonzalo"),
items=api.getAll(length=10),
items2=api.getStream(length=5)
)
我们可以把这个例子部署在docker服务器中。这里是docker-compose.yml
version: '3.6'
services:
frontend:
build:
context: .
dockerfile: Dockerfile
environment:
BACKEND_HOST: backend
ports:
- 5000:5000
command: gunicorn -w 4 app:app -b 0.0.0.0:5000
backend:
build:
context: .
dockerfile: Dockerfile
command: python server.py
源代码可在我的github上找到