背景
- 大部分情况可能是
同步服务端+异步客户端
,同步服务端+同步客户端
,异步服务端+异步客户端
, - 针对io密集型服务,
异步+异步
种方式效率最高, - 但是还有一种情况是,服务端是cpu密集型,客户端是io密集型,那么服务端如果采用异步,效率还不如同步高,客户端由于是io型,因此,我们可以选择
同步服务端+异步客户端
安装环境
python39 -m pip install grpcio==1.39.0 grpcio-tools==1.39.0 -i https://pypi.douban.com/simple
定义接口文件
helloworld.proto
syntax = "proto3";
package helloworld;
// The greeting service definition.
service Greeter {
// Sends a greeting
rpc SayHello (HelloRequest) returns (HelloReply) {}
}
// The request message containing the user's name.
message HelloRequest {
string name = 1;
}
// The response message containing the greetings
message HelloReply {
string message = 1;
}
服务端
服务端采用线程池,server.py
"""
python -m grpc_tools.protoc -I. --python_out=. --grpc_python_out=. pb/helloworld.proto
"""
from concurrent import futures
import time
import grpc
import helloworld_pb2 as helloworld_pb2
import helloworld_pb2_grpc as helloworld_pb2_grpc
_ONE_DAY_IN_SECONDS = 60 * 60 * 24
class Greeter(helloworld_pb2_grpc.GreeterServicer):
def SayHello(self, request, context):
import time
time.sleep(1) # 休眠1秒钟
return helloworld_pb2.HelloReply(message='Hello, %s!' % request.name)
def serve():
server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
helloworld_pb2_grpc.add_GreeterServicer_to_server(Greeter(), server)
server.add_insecure_port('[::]:50051')
print("GreeterServicer start at port 50051...")
server.start()
try:
while True:
time.sleep(_ONE_DAY_IN_SECONDS)
except KeyboardInterrupt:
server.stop(0)
if __name__ == '__main__':
serve()
客户端
客户端使用异步客户端,异步调用20次,
client.py代码如下:
import asyncio
import time
import grpc
import helloworld_pb2 as pb_dot_helloworld__pb2
import helloworld_pb2_grpc as pb_dot_helloworld_pb2__grpc
async def hello(name):
async with grpc.aio.insecure_channel('localhost:50051') as channel:
stub = pb_dot_helloworld_pb2__grpc.GreeterStub(channel)
response = await stub.SayHello(pb_dot_helloworld__pb2.HelloRequest(name=name))
print("GreeterService client received: " + response.message)
async def main():
await asyncio.gather(*[hello(f'zs{i}') for i in range(20)])
if __name__ == '__main__':
t = time.time()
asyncio.run(main())
print(time.time() - t)
运行
- 生成grpc代码
python -m grpc_tools.protoc -I. --python_out=. --grpc_python_out=. helloworld.proto
- 运行服务端
python server.py
- 运行客户端
pyhton client.py
- 客户端打印输出
代码分析
- 服务端
SayHello()
接口每次调用休眠1秒钟,10个线程提供服务,那么服务端每秒钟最多可以处理10个请求 - 客户端未开启多线程多进程,一共异步发出20个请求,客户端打印输出顺序不一致,并且打印结果显示2秒钟处理完毕,说明异步生效
总结
当我们碰到io密集型场景中又有cpu密集型任务的时候,我们可以采用此种同步服务端+异步客户端
的方案。
例如,当我们使用fastapi提供对外服务时,不仅有普通的对数据库增删查改等io操作,还有大量的本地数据计算等cpu型任务,那么我们可以把cpu型任务使用gprc封装成服务端,同时提供异步grpc客户端给fastapi调用,这样一来,虽然cpu计算还是那么慢,但是fastapi本身由于不被cpu任务拖累,却可以接受更多的io操作。
那么新的问题又来了,这种异步方式和使用消息中间件的异步方式,哪一种更好呢?欢迎讨论