昨天晚上和朋友闲聊聊到了celery,说了一个问题:celery不自动释放数据库连接,碰巧前些日子我们运维平台在用celery做一些异步任务时报了一个连接池溢出的问题,由于当时有别的项目在开发,草草的重启服务了事,今天利用摸鱼的时刻找找原因。
报错
这是当时报错图片,一个timeout的问题,应该是连接池没有可用连接且后面的session一直在阻塞导致的报错,这不是问题关键之处,关键是为啥会导致没有可用连接(这是一个Devops服务,开发人员上线服务用的,基本可以排除高并发带来的大量连接的问题),或者是说换一种思路:会不会一些数据库查询没有放开连接。
原因
面向百度并查阅sqlalchemy源码可以找到这样一段代码:
@app.teardown_appcontext
def shutdown_session(response_or_exc):
if app.config['SQLALCHEMY_COMMIT_ON_TEARDOWN']:
warnings.warn(
"'COMMIT_ON_TEARDOWN' is deprecated and will be"
" removed in version 3.1. Call"
" 'db.session.commit()'` directly instead.",
DeprecationWarning,
)
if response_or_exc is None:
self.session.commit()
self.session.remove()
return response_or_exc
这里利用的flask的钩子函数对每一个http响应做了处理,也就是调用了remove函数释放当前数据库连接,看到这段代码就真相大白了,连接池每一个session的释放是由flask做的,而我们异步任务由celery来做,数据库查询较多时自然就会出现这个问题,现在我们只需要在每一次异步任务结束时释放数据库连接就万事大吉了,celery官方文档说明了可以自定义task的基类,我们只需要继承于task的基类并通过after_return函数释放数据库连接:
class DBTask(Task):
"""Custom Class for closing database session when celery task returned."""
def after_return(self, status, retval, task_id, args, kwargs, einfo):
db.session.remove()
小结
归根到底不是celery的问题,sqlalchemy提供了每一个session的获取和释放,需要我们手动来调用,只不过flask这种生态优越的框架自己就帮我们做了。