我在芹菜过程中遇到了不希望的延迟,我无法解释。我的目的是管理传入数据的实时处理(以每秒10到60个数据的速率)。一个数据的处理被分成两个完全顺序的任务,但是并行化用于开始处理下一个数据(具有任务1),而处理当前数据(具有任务2)尚未完成。由于它是一个实时应用程序,因此在此过程中获得最短的延迟至关重要。
有一段时间,我在这个过程中遇到了冻结。为了看看这个问题来自哪里,我开始监视我的工人的职业。它似乎发生在工人之间的沟通中。我设计了最轻巧,最简单的例子来说明这一点。
这是我的代码,你可以看到我有两个任务,除了每个等待10毫秒。我每隔20ms就用一次芹菜链给他们打电话。我使用prerun和postrun以及日志来跟踪每个工人的职业。在大多数情况下,所有工作都按顺序发生,因为两个工人花费的时间都不超过发送速率。
from __future__ import absolute_import
import time
from celery import chain
from celery.signals import task_prerun, task_postrun
from celery import Celery
from kombu import Queue, Exchange
N_ITS = 100000 # Total number of chains sent
LOG_FILE = 'log_file.txt' # Path to the log file
def write_to_log_file(text):
with open(LOG_FILE, 'a') as f:
f.write(text)
# Create celery app
app = Celery('live')
app.config_from_object('celeryconfig')
default_exchange = Exchange('default', type='direct')
app.conf.task_queues = tuple(Queue(route['queue'], default_exchange, routing_key=route['queue'])
for route in app.conf.task_routes.values() + [{'queue': 'default'}])
app.conf.update(result_expires=3600)
# Define functions that record timings
@task_prerun.connect()
def task_prerun(signal=None, sender=None, task_id=None, task=None, **kwargs):
text = 'task_prerun; {0}; {1:.16g}\n'.format(task.name, time.time())
write_to_log_file(text)
@task_postrun.connect()
def task_postrun(signal=None, sender=None, task_id=None, task=None, **kwargs):
text = 'task_postrun; {0}; {1:.16g}\n'.format(task.name, time.time())
write_to_log_file(text)
# Define tasks
@app.task
def task_1(i):
print 'Executing task_1: {}'.format(i)
time.sleep(0.01)
@app.task
def task_2(i):
print 'Executing task_2: {}'.format(i)
time.sleep(0.01)
# Send chained tasks
def main():
celery_chains = []
for i in range(N_ITS):
print '[{}] - Dispatching tasks'.format(i)
celery_chains.append(chain(task_1.si(i) | task_2.si(i))())
time.sleep(0.02)
# wait for all tasks to complete
[c.get() for c in celery_chains]
if __name__ == '__main__':
main()
如果需要,我也会给出芹菜的配置:
from __future__ import absolute_import
import os
name = 'live'
broker_url = 'pyamqp://{}'.format(os.environ.get('RMQ_HOST', 'localhost'))
print 'broker_url:', broker_url
include = ['live']
DEFAULT_QUEUE = 'celery'
# A named queue that's not already defined in task_queues will be created automatically.
task_create_missing_queues = True
broker_pool_limit = 10000
task_routes = {
'live.task_1': {'queue': 'worker_1'},
'live.task_2': {'queue': 'worker_2'}
}
# We always set the routing key to be the queue name so we do it here automatically.
for v in task_routes.values():
v.update({'routing_key': v['queue']})
task_serializer = 'pickle'
result_serializer = 'pickle'
accept_content = ['json', 'pickle']
timezone = 'Europe/Paris'
enable_utc = True
对于经纪人,我使用docker image rabbitmq:3.6-alpine与基本配置appart,我启用了rabbitmq_management。
这将在以下工人职业计时表中重现:(颜色表示正在处理的数据的索引,因此您可以链接属于同一链的任务)
正如您所看到的,通常一切顺利,任务1在任务1完成后立即调用。但是,有时(图中箭头所示)任务2即使工作人员2没有被占用也不会立即开始。它推迟了27ms的延迟,这是单个任务持续时间的两倍多。这在执行期间大约每2秒发生一次。
我使用firehose进行了一些额外的调查,以研究rabbitmq中的消息交换,结果是消息有效地按时发送。根据我的理解,工作人员等待获取消息并处理任务,但我无法理解为什么。
我尝试将代理池限制设置为较高的数字,但问题仍然存在。