gRPC因其传输速度快,很适合业务量大、高并发的网络通信场景,线程池的实现方式性能受限,而AsyncIO异步方式是1个高性能的处理并发请求的框架,gRPC 应用了 python AsyncIO模块技术,编写并提供了一套异步API接口集— gRPC AsyncIO API,其性能稳定,非常适合于高并发、大流量的网络通信场景。
下面以实例来说明如何实现异步 gRPC的过程。
本文实例已在 windows10, ubuntu上运行测试通过。
1、准备 probobuf 接口文件
按下面内容新建 demo.proto 文件
syntax = "proto3";
package demo;
service RouteGuide {
rpc GetFeature(Point) returns (Feature) {}
}
message Point {
int32 latitude = 1;
int32 longitude = 2;
}
message Feature {
string name = 1;
Point location = 2;
}
编译proto 文件
python -m grpc_tools.protoc --proto_path=. --python_out=. --grpc_python_out=. demo.proto
生成如下两个文件:
demo_pb2.py
demo_pb2_grpc.py
2. 异步Server端实现
gRPC Server构造器
由于异步方式是通过协程来执行任务,实际上是单线程方式,构造器比较简单
grpc.aio.server(migration_thread_pool=None, handlers=None, interceptors=None, options=None, maximum_concurrent_rpcs=None, compression=None)
通常只需要设置 maximum_concurrent_rpcs 参数即可,即同时允许rpc并发调用数量,默认无限制
服务端的主要方法
add_insecure_port(address) 绑定网络地址
下面3个方法是异步方式实现:
async start() 启动服务
async stop(grace) 停止服务
async wait_for_termination(timeout=None) 中止event loop循环
服务器代码
接口消息类中,接口函数要用异步方式来执行。
注意:不要在异步接口函数中使用阻塞进程的语句。
from concurrent import futures
from datetime import datetime
import grpc
import demo_pb2
import demo_pb2_grpc
import asyncio
class RouteGuideServicer(demo_pb2_grpc.RouteGuideServicer):
"""Provides methods that implement functionality of route guide server."""
async def GetFeature(self, request, context):
print(datetime.now(),'\n',request)
return demo_pb2.Feature(name="abc", location=request)
async def server():
server = grpc.aio.server(maximum_concurrent_rpcs=10)
demo_pb2_grpc.add_RouteGuideServicer_to_server(
RouteGuideServicer(), server)
server.add_insecure_port('[::]:50051')
await server.start()
await server.wait_for_termination()
if __name__ == '__main__':
asyncio.run(server())
3. 异步客户端实现
当服务器采用异步方式时,gRPC的客户端采用普通方式,异步方式都可以。 本节介绍异步客户端的实现
异步客户端构建器
channel 类是客户端类,非加密客户端的构建方法:
grpc.aio.insecure_channel(target, options=None, compression=None, interceptors=None
客户端代码
客户端stub 层的接口调用函数要有异步方式执行。
from __future__ import print_function
import grpc
import demo_pb2
import demo_pb2_grpc
import asyncio
CHANNEL_OPTIONS = [('grpc.lb_policy_name', 'pick_first'),
('grpc.enable_retries', 0),
('grpc.keepalive_timeout_ms', 10000)]
async def guide_get_feature(stub):
point = demo_pb2.Point(latitude=409146138, longitude=-746188906)
feature = await stub.GetFeature(point)
if not feature.location:
print("Server returned incomplete feature")
return
if feature.name:
print("Feature called %s at %s" % (feature.name, feature.location))
else:
print("Found no feature at %s" % feature.location)
async def main():
# NOTE(gRPC Python Team): .close() is possible on a channel and should be
# used in circumstances in which the with statement does not fit the needs
# of the code.
# with grpc.aio.insecure_channel('localhost:50051') as channel:
async with grpc.aio.insecure_channel(target='localhost:50051',
options=CHANNEL_OPTIONS) as channel:
stub = demo_pb2_grpc.RouteGuideStub(channel)
print("-------------- GetFeature --------------")
await guide_get_feature(stub)
if __name__ == '__main__':
asyncio.run(main())
4. 测试代码
打开两个终端窗口,分别运行server.py, client.py
server.py 窗口
(enva) D:\workplace\python\Simple_project\grpc\gRPC_demo>py server_a.py
2023-02-05 23:57:34.983625
latitude: 409146138
longitude: -746188906
2023-02-05 23:57:36.518631
latitude: 409146138
longitude: -746188906
client.py 窗口
(enva) D:\workplace\python\Simple_project\grpc\gRPC_demo>py client_a.py
-------------- GetFeature --------------
Feature called abc at latitude: 409146138
longitude: -746188906
(enva) D:\workplace\python\Simple_project\grpc\gRPC_demo>py client_a.py
-------------- GetFeature --------------
Feature called abc at latitude: 409146138
longitude: -746188906
(enva) D:\workplace\python\Simple_project\grpc\gRPC_demo>
总结
异步gRPC与之前普通方式gRPC实际过程基本一致,主要的区别如下:
- 异步server构造器不需要线程池参数,异步协程是在同1个线程中执行
- 异步gRPC要求接口函数及调用都使用 async – await 来修饰
- 执行时使用异步 event loop,即通过ayscio.run( )来运行。