背景
- 近期遇到个问题:是tornado服务有个接口是计算密集型程序(计算文本相似度),因为tornado是单线程当并发请求接口时会造成生死检查接口无法ping通。
预解决方案
- 于是准备用torando-celery将任务放在celery进程中去跑
- 提交celery任务会返回任务id,通过torando的事件循环机制去轮询结果,如果查到就返回给前端,没查到则继续添加callback到ioloop中
- 代码大概长这个样子
from tornado.concurrent import Future
from tornado.ioloop import IOLoop
def async_(task, *args, **kwargs):
future = Future()
callback = kwargs.pop("callback", None)
if callback:
IOLoop.instance().add_future(future,
lambda future: callback(future.result()))
# celery返回task对象
result = task.delay(*args, **kwargs)
# 将轮询函数加入事件循环
IOLoop.instance().add_callback(_on_result, result, future)
return future
def _on_result(result, future):
# if result is not ready, add callback function to next loop,
# 内部操作是到celery配置的backend中去拿数据
if result.ready():
# 拿到了值会返回
future.set_result(result.result)
else:
# 没有拿到会继续添加回调,达到不断轮询结果的作用
IOLoop.instance().add_callback(_on_result, result, future)
- 基于这段代码往外延伸:异步任务还可以怎么实现?
- 消息队列+web服务
tornado服务提交一个耗时任务操作到消息队列中,消息体封装前端传来的请求体参数,消息提交成功返回message_id,另一个web服务监听消息队列,消费者拿到消息体中的请求参数,即可以模拟前端调用tornado的请求过程。(核心思想就是tornado我自己不处理耗时任务,我交给其他服务,可以是threadPool、ProcessPool、celery、或者其他任意服务都可以)
那么torando怎么去拿到异步服务的返回结果呢?
1.通过上面的方式去轮训,轮询的key就是消息队列返回的message_id
2.回调torando服务,把返回结果送过来
websocket的能力也可以在这里集成
- celery也是和上面同理的逻辑,web服务也就是celery的worker
- tornado的自身线程池扩展,本质也是yield挂起后,线程池有返回结果后send数据回来
遇到的坑
- 在测试数据库操作时是可以拿到正常的返回的
- 但是该任务调用了Hanlp模型,在加载模型的时候celery进程抛了异常的错误,并且无法被torando pycharm debug。
- Python提供了代码层面debug的工具pdb,解该问题预计是要做这个操作了
解决方案
- 现用dramatiq+redis解决了此问题,dramatiq-gevent my_app -p 8 -t 250,同时支持gevent的greenlet,2000个协程处理io请求,实现了真正的异步高并发。
历史遗留问题
- Python内存泄露的问题排查,无最佳实践