Celery工作者挂起ZEO数据库访问(竞争条件?)

时间:2013-06-20 19:36:54

标签: python database django celery zodb

问题

当使用访问ZEO服务器的软件包时,芹菜工作者正在执行任务。但是,如果我直接在tasks.py内访问服务器,则完全没有问题。

背景

我有一个读取和写入ZODB文件的程序。因为我希望多个用户能够同时访问和修改此数据库,所以我使用ZEO server进行管理,使其在多个进程和线程中安全。我在我的程序模块中定义数据库:

from ZEO import ClientStorage
from ZODB.DB import DB

addr = 'localhost', 8090
storage = ClientStorage.ClientStorage(addr, wait=False)
db = DB(storage)

SSCCE

我显然正在尝试更复杂的操作,但我们假设我只想要根对象或其子对象的键。我可以在这种情况下产生问题。

我在模块dummy_package中使用上述代码创建databases.py,以及用于执行数据库访问的简单模块:

# main.py

def get_keys(dict_like):
    return dict_like.keys()

如果我不尝试使用dummy_package进行任何数据库访问,我可以导入数据库并访问root而不会出现问题:

# tasks.py
from dummy_package import databases

@task()
def simple_task():

    connection = databases.db.open()
    keys = connection.root().keys()
    connection.close(); databases.db.close()
    return keys  # Works perfectly

但是,尝试传递root的连接或子项会使任务无限期挂起。

@task()
def simple_task():
    connection = databases.db.open()
    root = connection.root()
    ret = main.get_keys(root)  # Hangs indefinitely
    ...

如果它有任何区别,Django会访问这些Celery任务。

问题

所以,首先,这里发生了什么?以这种方式访问​​ZEO服务器会导致某种竞争条件吗?

可以让所有数据库访问Celery的责任,但这将导致丑陋的代码。此外,它会破坏我的程序作为独立程序运行的能力。是不是可以在Celery工作人员调用的例程中与ZEO交互?

2 个答案:

答案 0 :(得分:2)

不要将打开的连接或其根对象保存为全局。

每个线程需要一个连接;只是因为ZEO使多个线程可以访问,听起来你使用的是非线程本地的东西(例如databases.py中的模块级全局)。

将db保存为全局,但在每个任务期间调用db.open()。见http://zodb.readthedocs.org/en/latest/api.html#connection-pool

答案 1 :(得分:0)

我并不完全理解发生了什么,但我认为死锁与Celery默认使用multiprocessing进行并发的事实有关。切换到使用Eventlet来处理需要访问ZEO服务器的任务解决了我的问题。

我的流程

  1. 启动uses Eventlet的工作人员和使用标准multiproccesing的工作人员。

    celery是默认队列的名称(for historical reasons),因此让Eventlet工作者处理此队列:

    $ celery worker --concurrency=500 --pool=eventlet --loglevel=debug \ 
                     -Q celery                --hostname eventlet_worker
    $ celery worker  --loglevel=debug \
                     -Q multiprocessing_queue --hostname multiprocessing_worker
    
  2. Route tasks需要标准multiprocessing到相应的队列。默认情况下,所有其他人将路由到celery队列(Eventlet管理)。 (如果使用Django,则会进入settings.py):

    CELERY_ROUTES = {'project.tasks.ex_task': {'queue': 'multiprocessing_queue'}}