当使用访问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)
我显然正在尝试更复杂的操作,但我们假设我只想要根对象或其子对象的键。我可以在这种情况下产生问题。
我在模块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交互?
答案 0 :(得分:2)
不要将打开的连接或其根对象保存为全局。
每个线程需要一个连接;只是因为ZEO使多个线程可以访问,听起来你使用的是非线程本地的东西(例如databases.py中的模块级全局)。
将db保存为全局,但在每个任务期间调用db.open()。见http://zodb.readthedocs.org/en/latest/api.html#connection-pool
答案 1 :(得分:0)
我并不完全理解发生了什么,但我认为死锁与Celery默认使用multiprocessing
进行并发的事实有关。切换到使用Eventlet来处理需要访问ZEO服务器的任务解决了我的问题。
启动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
Route tasks需要标准multiprocessing
到相应的队列。默认情况下,所有其他人将路由到celery
队列(Eventlet管理)。 (如果使用Django,则会进入settings.py
):
CELERY_ROUTES = {'project.tasks.ex_task': {'queue': 'multiprocessing_queue'}}