Celery: Объект AttributeError 'list' не имеет атрибута 'decode' с серверной частью Redis

Созданный на 3 нояб. 2017  ·  55Комментарии  ·  Источник: celery/celery

Контрольный список

  • [X] Я включил вывод celery -A proj report в выпуск.
software -> celery:4.1.0 (latentcall) kombu:4.1.0 py:3.5.2
            billiard:3.5.0.3 redis:2.10.5
platform -> system:Linux arch:64bit, ELF imp:CPython
loader   -> celery.loaders.app.AppLoader
settings -> transport:redis results:redis://:**@****************

task_ignore_result: True
accept_content: {'pickle'}
result_serializer: 'pickle'
result_backend: 'redis://:********@************************'
task_serializer: 'pickle'
task_send_sent_event: True
broker_url: 'redis://:********@************************'

версия redis-server, как 2.x, так и 3.x

Действия по воспроизведению

Здравствуйте, я не уверен, что может вызвать проблемы, и уже пытался найти аналогичное решение, но пока безуспешно. Поэтому открывающая проблема здесь, надеюсь, это поможет

Проблема также описана там (не мной): https://github.com/andymccurdy/redis-py/issues/612

Пока что опыт таков, что это происходит в обоих случаях, когда бэкэнд задействован и не задействован (имеется в виду только при вызове apply_async(...) ).

Исключение при вызове apply_async()
attributeerror___list__object_has_no_attribute__decode_

Исключение при вызове .get() (также у этого есть int, а не list)
attributeerror___list__object_has_no_attribute__decode_

Надеюсь, это поможет

Ожидаемое поведение

Чтобы не выкидывать ошибку.

Фактическое поведение

AttributeError: объект 'list' не имеет атрибута 'decode'

Спасибо!

Redis Results Backend Bug Report

Все 55 Комментарий

Также есть полная трассировка стека, которая включает все параметры

attributeerror___list__object_has_no_attribute__decode

Это похоже на состояние гонки в пуле соединений Redis из-за одновременных рабочих операций. Какой тип пула рабочих вы используете? Думаю, что если вы воспользуетесь prefork pool, то не столкнетесь с этой проблемой. Дайте мне знать, каков будет результат, если вы попробуете.

Привет, @georgepsarakis ,

спасибо за ответ, похоже, мы работаем на prefork

при проверке вывода при запуске сельдерея (который работает под systemd) я получил этот вывод:

 -------------- celery@autoscaled-dashboard-worker v4.1.0 (latentcall)
---- **** -----
--- * ***  * -- Linux-4.4.0-45-generic-x86_64-with-Ubuntu-16.04-xenial 2017-11-04 20:41:15
-- * - **** ---
- ** ---------- [config]
- ** ---------- .> app:         worker:0x7f984dbcce48
- ** ---------- .> transport:   redis://:**@**
- ** ---------- .> results:     redis://:**@**
- *** --- * --- .> concurrency: {min=3, max=12} (prefork)
-- ******* ---- .> task events: ON

некоторая другая информация:

  • наши рабочие находятся на разных машинах, а затем на приложении (которое отправляет задачи)
  • иметь 2+ рабочих все время (все с одинаковыми настройками ниже)
  • все они работают под systemd ( celery multi start -A ... -E --autoscale=12,3 -Ofair + некоторые другие не очень важные агрументы, все указаны как Type=forking service)

дайте мне знать, могу ли я добавить что-нибудь еще, хотел бы как можно больше помочь решить эту проблему :)

Благодарность!

еще одна вещь (даже если я не уверен, может ли это быть связано) - иногда мы получаем это исключение (та же настройка, что и выше)

File "/usr/local/lib/python3.5/dist-packages/celery/app/base.py", line 737, in send_task
    amqp.send_task_message(P, name, message, **options)
  File "/usr/lib/python3.5/contextlib.py", line 77, in __exit__
    self.gen.throw(type, value, traceback)
  File "/usr/local/lib/python3.5/dist-packages/kombu/connection.py", line 419, in _reraise_as_library_errors
    sys.exc_info()[2])
  File "/usr/local/lib/python3.5/dist-packages/vine/five.py", line 178, in reraise
    raise value.with_traceback(tb)
File "/usr/local/lib/python3.5/dist-packages/kombu/connection.py", line 414, in _reraise_as_library_errors
    yield
  File "/usr/local/lib/python3.5/dist-packages/celery/app/base.py", line 736, in send_task
    self.backend.on_task_call(P, task_id)
  File "/usr/local/lib/python3.5/dist-packages/celery/backends/redis.py", line 189, in on_task_call
    self.result_consumer.consume_from(task_id)
  File "/usr/local/lib/python3.5/dist-packages/celery/backends/redis.py", line 76, in consume_from
    self._consume_from(task_id)
  File "/usr/local/lib/python3.5/dist-packages/celery/backends/redis.py", line 82, in _consume_from
    self._pubsub.subscribe(key)
  File "/usr/local/lib/python3.5/dist-packages/redis/client.py", line 2482, in subscribe
    ret_val = self.execute_command('SUBSCRIBE', *iterkeys(new_channels))
  File "/usr/local/lib/python3.5/dist-packages/redis/client.py", line 2404, in execute_command
    self._execute(connection, connection.send_command, *args)
  File "/usr/local/lib/python3.5/dist-packages/redis/client.py", line 2408, in _execute
    return command(*args)
  File "/usr/local/lib/python3.5/dist-packages/redis/connection.py", line 610, in send_command
    self.send_packed_command(self.pack_command(*args))
  File "/usr/local/lib/python3.5/dist-packages/redis/connection.py", line 585, in send_packed_command
    self.connect()
  File "/usr/local/lib/python3.5/dist-packages/redis/connection.py", line 493, in connect
    self.on_connect()
  File "/usr/local/lib/python3.5/dist-packages/redis/connection.py", line 567, in on_connect
    if nativestr(self.read_response()) != 'OK':
  File "/usr/local/lib/python3.5/dist-packages/redis/connection.py", line 629, in read_response
    raise response
kombu.exceptions.OperationalError: only (P)SUBSCRIBE / (P)UNSUBSCRIBE / PING / QUIT allowed in this context

мы попытались обновить py-redis (увидим, но это всего лишь второстепенный)

любой намек очень ценю :)

@Twista, можешь попробовать патч на

Если вы можете добавить сюда следующий код:

def on_after_fork(self):
    logger.info('Resetting Redis client.')
    del self.backend.client

Я надеюсь, что это заставит свойство client cached генерировать нового клиента Redis после каждой рабочей вилки.

Сообщите мне, если это даст какой-нибудь результат.

Привет, извините за долгий ответ.

Просто продолжение - мы просто пропатчили его и посмотрим, поможет ли. Надеюсь скоро :)

Спасибо за помощь :)

дайте нам знать ваш отзыв

Привет,

У меня примерно такая же проблема (с использованием Celery.send_task ), и у меня есть связанный с этим вопрос:
Почему при вызове асинхронной задачи (поэтому результат не ожидается ) Celery начинает прослушивать издателя Redis ( self._pubsub.subscribe(key) вызывается обработчиком сигнала celery/backends/redis.py , строка 189, в on_task_call )?

https://github.com/celery/celery/blob/2547666a1ea13b27bc13ef296ae43a163ecd4ab3/celery/backends/redis.py#L197

@georgepsarakis, к сожалению, патч не помог. :-(

вот еще одна трассировка стека:
screen shot 2018-03-12 at 10 56 04
screen shot 2018-03-12 at 10 56 26
screen shot 2018-03-12 at 10 56 45
screen shot 2018-03-12 at 10 57 06

Пожалуйста, дайте мне знать, если вы хотите протестировать некоторые логи или другие исправления.

@wimby большое спасибо за отзыв. Подскажите пожалуйста какие варианты вы используете для запуска воркера?

Привет @georgepsarakis
Отвечаю на этот вместо @wimby

вот и вся команда, которую мы используем для запуска сельдерея

celery multi start worker -A ... --pidfile=... --logfile=... --loglevel=... -E --time-limit=300 --autoscale=10,3 --queues=... -Ofair

столкнулся с той же проблемой, когда решил попробовать Redis Broker, пока откатился на кролик ..

Есть новости по этому поводу? Выполнение этого с аналогичной настройкой для @Twista / @wimby, где у нас есть рабочие cron, работающие на отдельной машине, чем процесс, который планирует наши задачи.

сельдерей == 4.0.2
redis == 2.10.5.

Чтобы уточнить, это происходит на машине, которая отправляет задачи, а не на рабочем компьютере.

Я тоже это вижу. Сельдерей 4.2.0, комбу 4.2.1, редис 2.10.6. Использование redis как для брокера, так и для результатов. Трафик Redis проходит между серверами по зашифрованному stunnel-соединению через порты.

Запуск в него в Django 1.11, работающем на mod_wsgi (2 процесса, 15 потоков в каждом), и это не ограничивается только указанным выше исключением. Я не могу скопировать и вставить трассировки полного стека. Приложение загружает веб-страницу с помощью кучи запросов ajax (90 запросов на загрузку страницы), каждый из которых запускает фоновую задачу через сельдерей. Задания выполняются успешно, но вернуть результат сложно.

При отправке задач я получил ConnectionError s, AttributeError s (список не имеет атрибута encode).

При получении результатов я получил InvalidResponse , ResponseError , ValueError , AttributeError , TypeError , IndexError , почти все из redis. Я также видел ошибки декодирования из комбу при попытке проанализировать ответы json. Я подозреваю, что трафик между запросами смешивается - в некоторых случаях я видел то, что выглядело как частичные ответы.

Я возвращаюсь к выполнению более объемных задач, что должно, по крайней мере, минимизировать это. Однако я видел случаи, когда проблемы с протоколом все равно возникали при минимальной нагрузке. Я бы не удивился, если бы это было вызвано основными проблемами с сетью. Я также использую дополнительную логику повтора для отправки запросов.

@deterb, если вы установили task_ignore_result в конфигурации сельдерея, это должно предотвратить это. Если вы не заботитесь о результатах задачи, в этом случае это явно не поможет.

Исправление, учитывающее ignore_result при планировании задач, было сделано в 4.2.0, так что все должно быть хорошо.

@asgoel Вся суть в том, чтобы получить результаты для большей части запросов (для них он не столько выполняется в фоновом режиме, сколько выполняет тяжелые вычисления на сервере, созданном для него, а не на веб-сервере). Я добавлю результат игнорирования для остальных и посмотрю, поможет ли это.

@deterb это похоже на проблему с небезопасными для потоков операциями. Можно ли попробовать не использовать многопоточность?

@georgepsarakis Я согласен, что это похоже на проблему с многопоточностью. Я попробую настроить mod_wsgi для работы с 15 процессами, по 1 потоку на процесс, и посмотрю, по-прежнему ли я наблюдаю такое поведение. Я не использую никаких дополнительных потоков или многопроцессорности вне mod_wsgi. Я видел похожее поведение (хотя и с меньшей частотой), работающее с сервером Django. Единственное взаимодействие с клиентами Redis в веб-приложении осуществляется через Celery, а именно отправка задач и получение их результатов. redis-py утверждает, что он потокобезопасен.

Я постараюсь провести дополнительное тестирование завтра и посмотрю, смогу ли я воссоздать его вне Django и без прокси-сервера stunnel.

@georgepsarakis Сегодня у меня не было возможности попробовать, но я считаю, что # 4670 (а именно, общие объекты PubSub не являющиеся потокобезопасными) связаны с # 4480. Скорее всего, то, что я вижу, отличается от исходного тикета и других упомянутых проблем. Хотя это, вероятно, связано с проблемами, поднятыми @asgoel , я думаю, что будет уместным отдельный билет для проблем параллелизма с бэкэндом результатов redis (оставьте # 4480, относящийся к бэкэнду результатов RPC). Я также могу начать извлекать соответствующие части трассировки стека.

Привет, ребята, я много раз сталкивался с этими ошибками.
Я использовал AWS Elasticache, и мое приложение было развернуто в AWS Elastic Beanstalk.
Мы изменили AWS Elasticache на Redis Labs и увеличили время ожидания AWS Elastic Load Balancer до 180 секунд, а сервера Apache - до 180 секунд.
Ошибок сильно уменьшилось.

Тем не менее время от времени они продолжались. Поэтому я решил изменить бэкэнд результата на PostgreSQL, и тогда ошибки полностью исчезли.

Интересная вещь: мы начали сталкиваться с этой проблемой сразу после развертывания нового выпуска, который

  • перешел с python 2.7 на python 3.6
    (и обновленный Django 1.11 (добавочный с 13 по .14))

Итак, интересно знать, что это может быть что-то, что зависит от версии Python. Надеюсь, это поможет разобраться в проблеме.

(В остальном мы используем сельдерей 4.2.1 и redis 2.10.6)

Кроме того, проблемная задача запускается Celery beat, и у нас была такая же проблема в задаче, запущенной другой задачей.

Всем спасибо за отзывы. Просто чтобы прояснить несколько вещей:

  • ResultConsumer - это компонент Redis Backend, который асинхронно получает результат.
  • ResultConsumer инициализирует экземпляр PubSub
  • ResultConsumer Экземпляры результаты не игнорируются.
  • один и тот же экземпляр PubSub не может использоваться одновременно из нескольких потоков
  • Что касается Рабочих, это изменение должно охватывать случай запуска новых вилок.

Я не знаю, можно ли выполнить соответствующие операции при запуске Django, возможно, этот обратный вызов может помочь; вызов ResultConsumer.on_after_fork затем создаст новые экземпляры, и проблема, скорее всего, не возникнет.

Хм.

В моем случае я попытался воспроизвести проблему, увеличив частоту выполнения задачи в Celery Beat. В настоящее время у меня происходит что-то вроде 1 сбоя на каждые 15 задач, но сбой происходит в рабочем, а не в Beat, и мой рабочий имеет concurrency равный 1 (у меня есть несколько независимых процессов, но все они "одиночные" ). htop сообщает мне, что есть основной процесс с единственным дочерним потоком. Это согласуется с вашей гипотезой?

Если я правильно понял ваш ответ (перечитываю, на всякий случай), абзац:

Я не знаю, можно ли выполнить соответствующие операции при запуске Django, возможно, этот обратный вызов может помочь; вызов ResultConsumer.on_after_fork затем создаст новые экземпляры, и проблема, скорее всего, не возникнет.

это было бы связано со случаем, когда у нас были бы ошибки при публикации задач из наших веб-процессов (gunicorn / uwsgi / ...), верно?

Что касается Рабочих, это изменение должно охватывать случай запуска новых вилок.

В наших случаях эти ошибки исходят от рабочих исключительно с Celery 4.2.1. Если я правильно понимаю ваше утверждение, то, возможно, там все еще есть проблема ...

В Celery 4.2.0 мне удалось обойти эту проблему, выполнив следующие действия:

  • Исправьте celery.backends.redis.ResultConsumer, чтобы сохранить _pubsub и подписаться в thread.local или добавить thread.local в список унаследованных классов.
  • После вызова задачи и обработки результатов звоните:
    result.backend.result_consumer.cancel_for(result.task_id)
    result.backend.result_consumer.stop()

Я не понял, как убрать призыв к остановке; если я удалю его, соединения начнут просачиваться (обратите внимание, что я работаю в среде mod_wsgi, подключающейся к redis через stunnel; в конечном итоге возникают проблемы с подключением после нескольких сотен запросов). Через некоторое время я перестаю подключаться через туннель. Я также не понял, как подключиться к управлению потоками mod_wsgi.

В тестовом примере, который я использую, есть веб-страница, которая выполняет ~ 1500 запросов Ajax по 5 за раз, выполняемых полностью в команде Django run_server. Без ограничения скорости условия гонки для конечных объектов pubsub бэкэнда возникают постоянно. Они происходят регулярно и с ограничением скорости.

Я подозреваю, что использование локального потока ResultConsumers для redis также может помочь с проблемами рабочего процесса Greenlet, хотя я не уверен, как можно надежно закрыть pubsubs.

Переключение RedisBackend на использование опроса для веб-приложения работает без изменений для конкретного приложения. Я заменил миксин асинхронного бэкэнда на base.SyncBackendMixin и закомментировал строки, относящиеся к получателю результата. Пока не будет исправлено более чистое решение, я, вероятно, переключу веб-приложение на использование версии RedisBackend SyncBackend и позволю рабочим использовать обычную версию (которая, похоже, не имеет этой проблемы.

Учитывая, что я использую Django, я решил использовать django-celery-results. Использование Redis меньше соответствует другим дизайнерским решениям, и в любом случае мы действительно мало что храним. Мы еще не запустили это в производство.

Результаты тяжелые и достаточно транзитивные, я не хочу помещать их в базу данных. В качестве более консервативного варианта я могу просто сохранить результаты напрямую, а не позволять Celery делать это до тех пор, пока это не будет исправлено (я уже использую Redis для некоторого кеширования).

У меня аналогичная проблема. Я надеюсь, что дополнительная информация, которую я предоставляю, может пролить больше света на проблему.

У меня есть один работник сельдерея (concurrency = 1) и сервер, который помещает задачи в очередь и получает результаты. Я использую rabbitmq в качестве брокера и redis для бэкэнда результата.

Я использую сельдерей 4.2.1 и hiredis 0.2.0 (проблема возникает и без hiredis).

Вызов службы на более медленной скорости работает должным образом, но как только я отправляю ~ 100 одновременных запросов, я начинаю видеть проблемы, описанные в этом выпуске. У меня есть пара разных исключений, которые, как мне кажется, связаны между собой.

  File "/___/___/celery_app.py", line 101, in send_task
    task_result.wait(timeout=timeout, propagate=True)
  File "/usr/lib/python3.6/site-packages/celery/result.py", line 224, in get
    on_message=on_message,
  File "/usr/lib/python3.6/site-packages/celery/backends/async.py", line 188, in wait_for_pending
    for _ in self._wait_for_pending(result, **kwargs):
  File "/usr/lib/python3.6/site-packages/celery/backends/async.py", line 255, in _wait_for_pending
    on_interval=on_interval):
  File "/usr/lib/python3.6/site-packages/celery/backends/async.py", line 56, in drain_events_until
    yield self.wait_for(p, wait, timeout=1)
  File "/usr/lib/python3.6/site-packages/celery/backends/async.py", line 65, in wait_for
    wait(timeout=timeout)
  File "/usr/lib/python3.6/site-packages/celery/backends/redis.py", line 119, in drain_events
    m = self._pubsub.get_message(timeout=timeout)
  File "/usr/lib/python3.6/site-packages/redis/client.py", line 2260, in get_message
    response = self.parse_response(block=False, timeout=timeout)
  File "/usr/lib/python3.6/site-packages/redis/client.py", line 2183, in parse_response
    return self._execute(connection, connection.read_response)
  File "/usr/lib/python3.6/site-packages/redis/client.py", line 2176, in _execute
    return command(*args)
  File "/usr/lib/python3.6/site-packages/redis/connection.py", line 579, in read_response
    self.disconnect()
  File "/usr/lib/python3.6/site-packages/redis/connection.py", line 530, in disconnect
    self._sock.close()
AttributeError: 'NoneType' object has no attribute 'close'

А также

Traceback (most recent call last):
  File "/usr/lib/python3.6/site-packages/redis/connection.py", line 344, in read_response
    bufflen = self._sock.recv_into(self._buffer)
OSError: [Errno 9] Bad file descriptor

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/usr/lib/python3.6/site-packages/redis/client.py", line 2165, in _execute
    return command(*args)
  File "/usr/lib/python3.6/site-packages/redis/connection.py", line 577, in read_response
    response = self._parser.read_response()
  File "/usr/lib/python3.6/site-packages/redis/connection.py", line 357, in read_response
    (e.args,))
redis.exceptions.ConnectionError: Error while reading from socket: (9, 'Bad file descriptor')

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/usr/lib/python3.6/site-packages/redis/connection.py", line 577, in read_response
    response = self._parser.read_response()
  File "/usr/lib/python3.6/site-packages/redis/connection.py", line 362, in read_response
    response = self._reader.gets()
redis.exceptions.InvalidResponse: Protocol error, got "t" as reply type byte

А также

  File "/usr/lib/python3.6/site-packages/celery/result.py", line 224, in get
    on_message=on_message,
  File "/usr/lib/python3.6/site-packages/celery/backends/async.py", line 188, in wait_for_pending
    for _ in self._wait_for_pending(result, **kwargs):
  File "/usr/lib/python3.6/site-packages/celery/backends/async.py", line 255, in _wait_for_pending
    on_interval=on_interval):
  File "/usr/lib/python3.6/site-packages/celery/backends/async.py", line 56, in drain_events_until
    yield self.wait_for(p, wait, timeout=1)
  File "/usr/lib/python3.6/site-packages/celery/backends/async.py", line 65, in wait_for
    wait(timeout=timeout)
  File "/usr/lib/python3.6/site-packages/celery/backends/redis.py", line 119, in drain_events
    m = self._pubsub.get_message(timeout=timeout)
  File "/usr/lib/python3.6/site-packages/redis/client.py", line 2260, in get_message
    response = self.parse_response(block=False, timeout=timeout)
  File "/usr/lib/python3.6/site-packages/redis/client.py", line 2183, in parse_response
    return self._execute(connection, connection.read_response)
  File "/usr/lib/python3.6/site-packages/redis/client.py", line 2176, in _execute
    return command(*args)
  File "/usr/lib/python3.6/site-packages/redis/connection.py", line 577, in read_response
    response = self._parser.read_response()
  File "/usr/lib/python3.6/site-packages/redis/connection.py", line 359, in read_response
    self._reader.feed(self._buffer, 0, bufflen)
AttributeError: 'NoneType' object has no attribute 'feed'

С точки зрения сельдерея, все эти задачи были выполнены успешно, и никаких исключений не было отправлено обратно. Я получаю эти исключения от процесса Python, который пытается получить результаты задачи.

Были ли какие-либо обновления по этой проблеме или возможные решения, которые я должен попробовать, не указанные в этой проблеме?

Вы работаете с несколькими потоками? Если это так, вы в некоторой степени соответствуете настройке Django, о которой я упоминал ранее. Если нет, то может быть что-то еще.

Мой текущий обходной путь заключается в том, чтобы никогда не использовать возвращаемые объекты результатов, а вместо этого отправлять и извлекать результаты непосредственно в / из структур данных Redis. Поскольку веб-запрос блокирует результаты, я обычно передаю «список» результатов, чтобы задача помещала ответы и вставляла в этот список в веб-запросе. Я буду использовать redis's blpop, чтобы получить результаты, пока я не получу их все или пока не закончу последний. Я использую флаг игнорирования результатов при отправке задачи, чтобы предотвратить создание канала прослушивания, и по-прежнему сохраню исходные задачи для регистрации идентификаторов и, возможно, возврата состояния позже.

Я не чувствовал себя комфортно с патчем, о котором я упоминал ранее; Мне нужно было что-то, что, как я знал, не будет иметь побочных эффектов.

@deterb Спасибо за ответ.

Я использую несколько потоков, поэтому считаю, что это очень похоже на то, что вы видите. Я потенциально мог бы сделать что-то подобное с дополнительным потоком, который, по сути, опросил бы redis на предмет результатов, но я бы очень хотел разобраться и в этом.

Я пошел дальше и собрал несколько контейнеров докеров, чтобы воспроизвести ошибку с помощью довольно простой настройки.
https://github.com/sihrc/celery_repro. Я сам продолжу изучать это через репо, но пока ничего не придумал. Мне потребуется немного времени, чтобы разобраться в тонкостях сельдерея и редиса.

Traceback (most recent call last):
  File "/usr/lib/python3.6/runpy.py", line 193, in _run_module_as_main
    "__main__", mod_spec)
  File "/usr/lib/python3.6/runpy.py", line 85, in _run_code
    exec(code, run_globals)
  File "/app/celery_race_condition/app.py", line 21, in <module>
    results = [result.result() for result in futures]
  File "/app/celery_race_condition/app.py", line 21, in <listcomp>
    results = [result.result() for result in futures]
  File "/usr/lib/python3.6/concurrent/futures/_base.py", line 432, in result
    return self.__get_result()
  File "/usr/lib/python3.6/concurrent/futures/_base.py", line 384, in __get_result
    raise self._exception
  File "/usr/lib/python3.6/concurrent/futures/thread.py", line 56, in run
    result = self.fn(*self.args, **self.kwargs)
  File "/app/celery_race_condition/app.py", line 11, in call_and_retrieve
    async_result.wait(timeout=100, propagate=True)
  File "/usr/lib/python3.6/site-packages/celery/result.py", line 224, in get
    on_message=on_message,
  File "/usr/lib/python3.6/site-packages/celery/backends/async.py", line 188, in wait_for_pending
    for _ in self._wait_for_pending(result, **kwargs):
  File "/usr/lib/python3.6/site-packages/celery/backends/async.py", line 255, in _wait_for_pending
    on_interval=on_interval):
  File "/usr/lib/python3.6/site-packages/celery/backends/async.py", line 56, in drain_events_until
    yield self.wait_for(p, wait, timeout=1)
  File "/usr/lib/python3.6/site-packages/celery/backends/async.py", line 65, in wait_for
    wait(timeout=timeout)
  File "/usr/lib/python3.6/site-packages/celery/backends/redis.py", line 119, in drain_events
    m = self._pubsub.get_message(timeout=timeout)
  File "/usr/lib/python3.6/site-packages/redis/client.py", line 2513, in get_message
    response = self.parse_response(block=False, timeout=timeout)
  File "/usr/lib/python3.6/site-packages/redis/client.py", line 2428, in parse_response
    if not block and not connection.can_read(timeout=timeout):
  File "/usr/lib/python3.6/site-packages/redis/connection.py", line 618, in can_read
    return self._parser.can_read() or \
  File "/usr/lib/python3.6/site-packages/redis/connection.py", line 372, in can_read
    self._next_response = self._reader.gets()
redis.exceptions.InvalidResponse: Protocol error, got "\r" as reply type byte

С тех пор я занимаюсь отладкой и обнаружил, что как только поток, который использовался ранее, снова используется для получения результата, мы начинаем получать ошибки. Я проверил это, создав пул потоков из 100 и одновременно выполняя 101 задачу.

Для меня это сводится к потоковой безопасности ResultConsumer и, возможно, Redis 'клиента PubSub. В моем текущем обходном пути используется отдельный ResultConsumer для каждого локального контекста потока.

import threading
from celery.backends.redis import RedisBackend

local_context = threading.local()
class Backend(RedisBackend):
    <strong i="7">@property</strong>
    def result_consumer(self):
        consumer = getattr(local_context, "consumer", None)
        if consumer:
            return consumer

        local_context.consumer = self.ResultConsumer(
            self, self.app, self.accept,
            self._pending_results, self._pending_messages,
        )

        return local_context.consumer

    @result_consumer.setter
    def result_consumer(self, value):
        local_context.consumer = value

Celery(
    ...,
    result_backend=__name__ + ".Backend",
    ...
)

К сожалению, я не смог вникнуть в подробности, чтобы понять, почему это проблема для начала, но, надеюсь, этот обходной путь будет полезен для кого-то другого.

Celery довольно агрессивно относится к регистрации каналов в интерфейсе PubSub, который, как указано в документации redis-py,

Опубликованное вами решение имеет ту же тему, что и я, но выглядит чище.

@sihrc @deterb Я думаю, вы на правильном пути, это может быть связано с тем, что клиент PubSub не является потокобезопасным. @sihrc, использующий думали / пытались передать логику экземпляру клиента ResultConsumer и PubSub ? В любом случае, если бы вы могли просто предоставить PR с этим решением, это было бы здорово!

@georgepsarakis Я попытался перенести логику в ResultConsumer и подбросил ее в PR. Полный отказ от ответственности: я не уверен, есть ли у меня время, чтобы вовремя активно протолкнуть этот пиар, но я сделаю все, что в моих силах.

@sihrc, спасибо! Это совершенно понятно, просто дайте нам знать, если вы не можете посвятить больше времени в какой-либо момент вперед.

Похоже, PR для решения неактивен. Есть ли какие-нибудь намерения решить эту проблему?

Наверное, после релиза.

@thedrow Спасибо за быстрый ответ. Вы хотите сказать, что после выпуска 4.3 (надеюсь, не 5.0) участники обратят внимание на эту ошибку?

да.

Мы также видели это в версии 4.2 недавно, в прошлом месяце.

@thedrow у вас есть приблизительная оценка сроков выхода версии 4.3?

4.3 выпущен еще в марте. Кроме того, 4.4rc2 находится на pypi, а 4.4 выйдет на этой неделе.

@auvipy Отлично , спасибо. Решается ли эта проблема в настоящее время?

не уверен но планируется на 4.5.

FWIW, даже с приведенным выше фрагментом кода , мы все еще видим периодические ошибки протокола, хотя и реже:

Traceback (most recent call last): File "/opt/python/current/app/app/XXXX", line #, in _check_celery result = async_result.get(timeout=self.service_timeout) File "/opt/python/run/venv/local/lib/python3.6/site-packages/celery/result.py", line 226, in get on_message=on_message, File "/opt/python/run/venv/local/lib/python3.6/site-packages/celery/backends/asynchronous.py", line 188, in wait_for_pending for _ in self._wait_for_pending(result, **kwargs): File "/opt/python/run/venv/local/lib/python3.6/site-packages/celery/backends/asynchronous.py", line 255, in _wait_for_pending on_interval=on_interval): File "/opt/python/run/venv/local/lib/python3.6/site-packages/celery/backends/asynchronous.py", line 56, in drain_events_until yield self.wait_for(p, wait, timeout=1) File "/opt/python/run/venv/local/lib/python3.6/site-packages/celery/backends/asynchronous.py", line 65, in wait_for wait(timeout=timeout) File "/opt/python/run/venv/local/lib/python3.6/site-packages/celery/backends/redis.py", line 127, in drain_events message = self._pubsub.get_message(timeout=timeout) File "/opt/python/run/venv/local/lib/python3.6/site-packages/redis/client.py", line 3297, in get_message response = self.parse_response(block=False, timeout=timeout) File "/opt/python/run/venv/local/lib/python3.6/site-packages/redis/client.py", line 3185, in parse_response response = self._execute(conn, conn.read_response) File "/opt/python/run/venv/local/lib/python3.6/site-packages/redis/client.py", line 3159, in _execute return command(*args, **kwargs) File "/opt/python/run/venv/local/lib/python3.6/site-packages/redis/connection.py", line 700, in read_response response = self._parser.read_response() File "/opt/python/run/venv/local/lib/python3.6/site-packages/redis/connection.py", line 318, in read_response (str(byte), str(response))) redis.exceptions.InvalidResponse: Protocol Error: , b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00*3'

Ответ «все нули» - самый распространенный, хотя я только что видел, как проходит ProtocolError: 1, b'575932362]' .

Я не сбрасываю со счетов возможность того, что это может быть просто сбой в Redis и не имеет ничего общего с Celery. Сложно сказать.

Это использует Celery 4.3.0, Kombu 4.6.6 и Redis 3.3.11.

Я начал часто получать эту ошибку после реализации асинхронного вызова. Приложение фляжки обращается к сельдерею. Иногда это срабатывает, а иногда я получаю результат x00:

web_1     | Traceback (most recent call last):
web_1     |   File "/root/.local/share/virtualenvs/app-lp47FrbD/lib/python3.7/site-packages/flask/app.py", line 2463, in __call__
web_1     |     return self.wsgi_app(environ, start_response)
web_1     |   File "/root/.local/share/virtualenvs/app-lp47FrbD/lib/python3.7/site-packages/flask/app.py", line 2449, in wsgi_app
web_1     |     response = self.handle_exception(e)
web_1     |   File "/root/.local/share/virtualenvs/app-lp47FrbD/lib/python3.7/site-packages/flask/app.py", line 1866, in handle_exception
web_1     |     reraise(exc_type, exc_value, tb)
web_1     |   File "/root/.local/share/virtualenvs/app-lp47FrbD/lib/python3.7/site-packages/flask/_compat.py", line 39, in reraise
web_1     |     raise value
web_1     |   File "/root/.local/share/virtualenvs/app-lp47FrbD/lib/python3.7/site-packages/flask/app.py", line 2446, in wsgi_app
web_1     |     response = self.full_dispatch_request()
web_1     |   File "/root/.local/share/virtualenvs/app-lp47FrbD/lib/python3.7/site-packages/flask/app.py", line 1951, in full_dispatch_request
web_1     |     rv = self.handle_user_exception(e)
web_1     |   File "/root/.local/share/virtualenvs/app-lp47FrbD/lib/python3.7/site-packages/flask/app.py", line 1820, in handle_user_exception
web_1     |     reraise(exc_type, exc_value, tb)
web_1     |   File "/root/.local/share/virtualenvs/app-lp47FrbD/lib/python3.7/site-packages/flask/_compat.py", line 39, in reraise
web_1     |     raise value
web_1     |   File "/root/.local/share/virtualenvs/app-lp47FrbD/lib/python3.7/site-packages/flask/app.py", line 1949, in full_dispatch_request
web_1     |     rv = self.dispatch_request()
web_1     |   File "/root/.local/share/virtualenvs/app-lp47FrbD/lib/python3.7/site-packages/flask/app.py", line 1935, in dispatch_request
web_1     |     return self.view_functions[rule.endpoint](**req.view_args)
web_1     |   File "/usr/src/app/web.py", line 81, in balances
web_1     |     result = group(balance.s(name) for name in factories.LAZY_STRATEGY_MAP.keys())().get()
web_1     |   File "/root/.local/share/virtualenvs/app-lp47FrbD/lib/python3.7/site-packages/celery/result.py", line 703, in get
web_1     |     on_interval=on_interval,
web_1     |   File "/root/.local/share/virtualenvs/app-lp47FrbD/lib/python3.7/site-packages/celery/result.py", line 822, in join_native
web_1     |     on_message, on_interval):
web_1     |   File "/root/.local/share/virtualenvs/app-lp47FrbD/lib/python3.7/site-packages/celery/backends/asynchronous.py", line 151, in iter_native
web_1     |     for _ in self._wait_for_pending(result, no_ack=no_ack, **kwargs):
web_1     |   File "/root/.local/share/virtualenvs/app-lp47FrbD/lib/python3.7/site-packages/celery/backends/asynchronous.py", line 268, in _wait_for_pending
web_1     |     on_interval=on_interval):
web_1     |   File "/root/.local/share/virtualenvs/app-lp47FrbD/lib/python3.7/site-packages/celery/backends/asynchronous.py", line 55, in drain_events_until
web_1     |     yield self.wait_for(p, wait, timeout=interval)
web_1     |   File "/root/.local/share/virtualenvs/app-lp47FrbD/lib/python3.7/site-packages/celery/backends/asynchronous.py", line 64, in wait_for
web_1     |     wait(timeout=timeout)
web_1     |   File "/root/.local/share/virtualenvs/app-lp47FrbD/lib/python3.7/site-packages/celery/backends/redis.py", line 161, in drain_events
web_1     |     message = self._pubsub.get_message(timeout=timeout)
web_1     |   File "/root/.local/share/virtualenvs/app-lp47FrbD/lib/python3.7/site-packages/redis/client.py", line 3565, in get_message
web_1     |     response = self.parse_response(block=False, timeout=timeout)
web_1     |   File "/root/.local/share/virtualenvs/app-lp47FrbD/lib/python3.7/site-packages/redis/client.py", line 3453, in parse_response
web_1     |     response = self._execute(conn, conn.read_response)
web_1     |   File "/root/.local/share/virtualenvs/app-lp47FrbD/lib/python3.7/site-packages/redis/client.py", line 3427, in _execute
web_1     |     return command(*args, **kwargs)
web_1     |   File "/root/.local/share/virtualenvs/app-lp47FrbD/lib/python3.7/site-packages/redis/connection.py", line 734, in read_response
web_1     |     response = self._parser.read_response()
web_1     |   File "/root/.local/share/virtualenvs/app-lp47FrbD/lib/python3.7/site-packages/redis/connection.py", line 324, in read_response
web_1     |     (str(byte), str(response)))
web_1     | redis.exceptions.InvalidResponse: Protocol Error: , b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00*3'

Более странные ошибки:

web_1 | redis.exceptions.InvalidResponse: Protocol Error: s, b'ubscribe'

А также:

web_1     |   File "/root/.local/share/virtualenvs/app-lp47FrbD/lib/python3.7/site-packages/redis/connection.py", line 350, in read_response
web_1     |     response = self._buffer.read(length)
web_1     | AttributeError: 'NoneType' object has no attribute 'read'

Python 3.7, Celery 4.4.1, Redis 3.4.1.

Кто-нибудь из вас может попробовать этот патч https://github.com/celery/celery/pull/5145?

Кто-нибудь из вас может попробовать этот патч №5145?

Я бы не прочь попробовать, но отмечу, что в этом патче много разногласий, сборки с этим патчем терпят неудачу, и он простоял 17 месяцев. Похоже, что от идеи отказались.

сначала попробуйте и не прочь просмотреть PR и подготовить для него неудачный тест.

FWIW, сегодня подтвердил, что это все еще проблема с Celery 4.4.2, Kombu 4.6.8, Redis 3.4.1.

Учитывая, сколько лет этой проблеме, есть ли намек на ее причины? Я не думаю, что мой проект видел это [много] в последнее время, но, тем не менее, по крайней мере в паре проектов по-прежнему возникает эта ошибка.

@jheld Комментарии из https://github.com/celery/celery/issues/4363#issuecomment -411708951 соответствуют тому, что я видел в прошлый раз, когда ткнул в него, а именно, полученный потребитель инициализирует PubSub, который в конечном итоге распределяется между потоками и что PubSub не являются потокобезопасными. Мое обходное решение игнорировало результаты и сохраняло / просматривало результаты независимо от моего приложения Django.

Всем спасибо за отзывы. Просто чтобы прояснить несколько вещей:

  • ResultConsumer - это компонент Redis Backend, который асинхронно получает результат.
  • ResultConsumer инициализирует экземпляр PubSub
  • ResultConsumer Экземпляры результаты не игнорируются.
  • один и тот же экземпляр PubSub не может использоваться одновременно из нескольких потоков
  • Что касается Рабочих, это изменение должно охватывать случай запуска новых вилок.

Я не знаю, можно ли выполнить соответствующие операции при запуске Django, возможно, этот обратный вызов может помочь; вызов ResultConsumer.on_after_fork затем создаст новые экземпляры, и проблема, скорее всего, не возникнет.

Ранее в этом выпуске кто-то упомянул, что это началось после обновления Django:

и обновил Django 1.11 (с 13 по .14 инкрементально))

Мы только что обновились с 1.11 до 2.2 и начали это видеть. Не могу представить почему, но подумал, что повторю сказанное выше.

Я только что проверил это https://github.com/andymccurdy/redis-py/issues/612#issuecomment -515019364, но не совсем уверен

Была ли эта страница полезной?
0 / 5 - 0 рейтинги