我正在使用Celery独立版(不在Django中)。我计划在多台物理机上运行一个工作任务类型。该任务执行以下操作
我正在使用PostgreSQL,但这同样适用于使用连接的其他商店类型。过去,我使用数据库连接池来避免在每个请求上创建新的数据库连接,或者避免将连接打开太长时间。但是,由于每个Celery工作程序都在一个单独的进程中运行,我不确定它们实际上是如何共享池的。我错过了什么吗?我知道Celery允许你坚持从芹菜工人那里得到的结果,但这不是我想在这里做的。根据处理的数据,每个任务可以执行多个不同的更新或插入。
从Celery工作者中访问数据库的正确方法是什么?
是否可以跨多个工作人员/任务共享一个池,还是有其他方法可以做到这一点?
答案 0 :(得分:23)
我喜欢tigeronk2关于每个工人一个连接的想法。正如他所说,Celery维护着自己的工作池,因此不需要单独的数据库连接池。 Celery Signal docs解释了在创建worker时如何进行自定义初始化,所以我将以下代码添加到我的tasks.py中,它似乎与您期望的完全一样。当工人关机时,我甚至能够关闭连接:
db_conn = None
@worker_process_init.connect
def init_worker(**kwargs):
global db_conn
print('Initializing database connection for worker.')
db_conn = db.connect(DB_CONNECT_STRING)
@worker_process_shutdown.connect
def shutdown_worker(**kwargs):
global db_conn
if db_conn:
print('Closing database connectionn for worker.')
db_conn.close()
答案 1 :(得分:2)
您可以在celery config中覆盖默认行为以使每个进程具有线程化工作者而不是工作者:
CELERYD_POOL = "celery.concurrency.threads.TaskPool"
然后,您可以将共享池实例存储在任务实例上,并从每个线程任务调用中引用它。
答案 2 :(得分:2)
每个工作进程都有一个数据库连接。由于芹菜本身维护着一个工作进程池,因此您的数据库连接将始终等于芹菜工作者的数量。 翻转方面,它会将数据库连接池绑定到芹菜工作进程管理。但是,鉴于GIL在一个过程中一次只允许一个线程,这应该没问题。
答案 3 :(得分:1)
答案 4 :(得分:0)
也许,celery.concurrency.gevent可以提供池共享而不会加剧GIL。但是,它的支持仍然是“实验性的”。
和psycopg2.pool.SimpleConnectionPool分享greenlets(coroutines),它们将在一个进程/线程中运行。
关于该主题的其他stack讨论的一点点。
答案 5 :(得分:0)
通过实施和监督来回馈我的发现。
欢迎反馈。
参考: 使用合并http://www.prschmid.com/2013/04/using-sqlalchemy-with-celery-tasks.html
每个工作进程(由-c k指定的prefork模式)将建立一个到DB的新连接,而不会合并或重用。 因此,如果使用池,则只能在每个工作进程级别看到池。那么游泳池大小> 1没用,但重用连接仍然可以保存连接从open&关闭。
如果每个工作进程使用一个连接,则在初始化阶段为每个工作进程建立1个DB连接(prefork模式celery -A app worker -c k)。 它保存了open& amp;反复关闭。
无论有多少工作线程(eventlet),每个工作线程(celery -A app worker -P eventlet)只建立一个与DB的连接而不进行池化或重用。 因此对于eventlet,一个芹菜进程(celery -A app worker ...)上的所有工作线程(eventlets)每次都有1个db连接。
根据芹菜文档
但是你需要确保你的任务不会执行阻塞调用 这将暂停工作人员的所有其他操作,直到阻止 呼叫返回。
这可能是由于MYSQL数据库连接阻塞调用的方式。