Celery: Особенность: Beat должен избегать одновременных вызовов

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

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

celerybeat должен либо обеспечивать механизм предотвращения непреднамеренного параллелизма, либо документация должна предлагать передовой подход.

Celerybeat

Самый полезный комментарий

@ ankur11 single-beat гарантирует, что работает только один экземпляр сельдерея, но не синхронизирует состояние расписания между экземплярами.

Если бы я использовал планировщик по умолчанию с периодической задачей, предназначенной для запуска каждые 15 минут, и имел бы аварийное переключение с однократным запуском через 14 минут после последнего запуска задачи, задача не запускалась бы до 15 минут после нового удара сельдерея. Экземпляр запущен, в результате чего перерыв в 29 минут.

Чтобы разделить состояние расписания между экземплярами, мне нужно было использовать альтернативный планировщик . django-celery-beat - альтернатива, упомянутая в документации по Celery, но я предпочел использовать Redis в качестве бэкэнда для синхронизации расписания, поскольку я уже использовал Redis в качестве бэкэнда Celery.

Redbeat включает в себя как общее состояние расписания, поддерживаемое Redis, так и блокировку, чтобы гарантировать, что только один экземпляр будет планировать задачи, поэтому мне не понадобились Single-beat или BeatCop, как только я начал его использовать.

В нашей реализации «сельдерейный ритм» запускается супервизором во всех случаях с Redbeat в качестве планировщика (например, exec celery beat --scheduler=redbeat.RedBeatScheduler --app=myproject.celery:app ). К сожалению, я не могу поделиться кодом, связанным с работой, но я рад ответить на любые дополнительные вопросы о реализации в целом.

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

Это может быть решено с помощью kombu.pidbox Таким же образом celeryd обнаруживает, что уже запущен узел с таким же именем. Поскольку celerybeat является централизованным, он может использовать фиксированное имя узла.

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

Требуется дополнительное планирование, поскольку существует вариант использования для запуска нескольких экземпляров в одном кластере. Например, для «сегментирования» расписания на несколько частей. Должна быть как минимум возможность выбрать имя узла для каждого экземпляра. Перенос на 2.3.0.

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

Позволит ли подход kombu.pidbox запускать несколько экземпляров celerybeat которые будут просто спать, если обнаружит, что экземпляр уже запущен с фиксированным именем узла, и проведет опрос для продвижения себя в активный, если это экземпляр выходит из строя?

Запуск нескольких активных экземпляров звучит интересно - какие еще преимущества могут быть, помимо совместного использования расписания?

+1

+1

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

+9999;)
Что-то не так с использованием решения kombu.pidbox ? Даже без шардинга и необычных функций это было бы здорово и очень удобно. Прямо сейчас мне нужно вручную запустить celerybeat на другом хосте.

Можно использовать Pidbox, но проблема в том, что beat не является потребителем. Чтобы отвечать на широковещательные сообщения типа «Здесь есть какие-нибудь биты?» ему придется постоянно прослушивать сообщения в своей очереди широковещательной рассылки, а в настоящее время он не может, потому что занят планированием сообщений.

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

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

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

Блокировку в amqp можно создать, объявив очередь, например queue_declare ('celerybeat.lock', arguments = {'x-expires': 2000} ``

+1

Я бы хотел увидеть это

+1

+1

+1 тоже

+1

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

Кто-нибудь вообще отошел от сельдерея из-за этого? Мне было бы интересно узнать об этом.

РЕДАКТИРОВАТЬ:

Я нашел эту суть (https://gist.github.com/winhamwr/2719812) через обсуждение в Google (https://www.google.co.in/search?q=celerybeat+lock&aq=f&oq=celerybeat+lock&aqs= chrome.0.57j62l3.2125j0 & sourceid = chrome & ie = UTF-8).

Мне также интересно, использовал ли кто-нибудь общий pid-файл для celerybeat напрямую, может быть, с EBS на AWS или, может быть, в ведре S3… celerybeat --pidfile=/path/to/shared/volume .

Я заметил, что у текущего мастера (3.1 dev) есть шаг для сплетен для потребителя. Можно ли было бы использовать очередь сплетен и выборы лидера для координации встроенных процессов избиения? То есть каждый рабочий будет запускать встроенный битовый процесс, но только лидер будет ставить в очередь периодическую задачу. Скорее всего, это предполагает общее хранилище расписаний.

@mlavin Это может сработать, но только для транспорта брокера, поддерживающего широковещательную

Проблема с решением pidbox заключается в том, что программа celerybeat должна быть переписана для использования асинхронного ввода-вывода.
В настоящее время он не может одновременно потреблять задачи и производить их, поскольку планировщик блокирует.

В большинстве случаев это вообще не является необходимой функцией, поскольку в большинстве производственных развертываний есть выделенный хост для бит-процесса, а затем достаточно использовать --pidfile , чтобы убедиться, что вы не запускаете несколько экземпляров.

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

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

Можно использовать uWSGI, чтобы иметь однократный процесс с откатом на другой узел (узлы)

+1, мы запускаем идентичные инстансы Amazon EC2 и было бы неплохо, если бы периодические задачи выполнялись только на одном узле. Тем временем я попробую использовать uWSGI, спасибо за предложение.

+1

+1

На работе я приводил доводы в пользу использования Celerybeat для планирования, но отсутствие HA из коробки делает это очень трудным. На самом деле, похоже, из-за этого мы его вообще откажемся. Проще говоря, запуск только 1 экземпляра Celerybeat делает это единственной точкой отказа и, следовательно, не готовым для нас производством.

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

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

Спасибо @ 23doors.

Мои задачи уже поддерживают блокировку Redis, чтобы предотвратить запуск другого экземпляра задачи. Если я запускаю 2 удара на 2 разных машинах и мои задачи запланированы на 5-минутные интервалы, я думаю, что это сработает, даже если оба удара будут помещать задачи в очередь. Доводить дело до принятия становится еще труднее, когда вам нужно реализовать обходной путь для своей основной функциональности.

Я изучу рекомендацию по подклассу. Это может быть более чистый подход.

Спасибо за предложения!

В Lulu мы решили эту проблему, написав простой диспетчер синглтонов кластера (названный BeatCop). Он использует блокировку Redis с истекающим сроком действия, чтобы гарантировать, что в пуле автомасштабируемых рабочих Celery работает только один Celerybeat. Если что-то происходит с этим Celerybeat (например, масштабирование экземпляра, его смерть или сбой Celerybeat), другой узел автоматически порождает новый Celerybeat. Мы открыли исходный код BeatCop .

@ingmar, мы написали это https://github.com/ybrs/single-beat по тем же причинам, в прошлый раз, когда я проверял, я не видел вашего комментария. мы также выпустили, поскольку открытый исходный код может быть полезен для некоторых других. более или менее делает то же самое.

насколько я вижу, основные отличия от beatcop - мы используем pyuv - поэтому beatcop более переносим, ​​я думаю, меньше зависимостей -, перенаправляем дочерние stderr и stdout в качестве родителей и выходим, если ребенок умирает с тем же кодом, настройте его с помощью переменные env. так что добавить к супервизору немного проще.

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

+1

+1

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

@ingmar Спасибо за это! Я собираюсь попробовать это в моем рабочем кластере.

+10, поскольку текущая реализация означает единую точку отказа, которая уходит от того, почему мы в первую очередь используем распределенную очередь

+1

+1

Похоже, это будет в версии 5.0.0 https://github.com/celery/celery/milestones/v5.0.0

+1

Закрытие этого, как и при имеющихся ресурсах, займет 10 лет.

Извините, но это серьезная проблема для так называемой "распределенной" очереди. Как бы долго это ни было реализовано, в конечном итоге это должно быть исправлено. Закрывать вполне допустимую проблему, потому что у вас нет ресурсов _ прямо сейчас_, кажется неправильным. Не могли бы вы снова открыть его и применить метку, указывающую, что в данный момент у него низкий приоритет?

Я знаю, что моя причина закрытия была до абсурда неожиданной, поэтому как пользователь программного обеспечения я могу понять ваше мнение, но технически Beat больше похожа на дополнительную функцию. Он полностью отделен от остальной части Celery и был намеренно спроектирован так, чтобы не распространяться, чтобы упростить реализацию. Это началось как изящный способ определения cronjobs из Python в качестве бонуса для пользователей, уже использующих Celery, затем все больше и больше людей использовали Celery в качестве замены cron.

Проблема остается открытой уже ШЕСТЬ лет, и, хотя ее часто просят и от нее зависят бесчисленные компании, ни одна из них никогда не предлагала заплатить за ее реализацию.

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

Дубликат этого выпуска я сегодня тоже закрыл, см. №1495. Были запросы на вытягивание, пытающиеся решить эту проблему, и некоторые из них являются многообещающими, но, учитывая самоотверженность, необходимую для доказательства того, что данная реализация работает, у меня все еще не было времени, чтобы просмотреть их должным образом. Может быть, это подтолкнет кого-то к действию, даже если нет, я думаю, что это лучше, чем держать запрос функции открытым в течение шести лет, когда никто не работает над этим. Это также своего рода медвежья услуга для пользователей, которые хотят, чтобы это было исправлено.

@ask Достаточно

Спасибо, что нашли время подробно объяснить свои аргументы.

@ask Мне было интересно, можно ли обойти эту проблему, разместив файл celerybeat-schedule (используемый celery.beat.PersistentScheduler ) внутри тома NFS, который используется всеми узлами кластера?

Класс PersistentScheduler использует shelve в качестве модуля базы данных, поэтому одновременная запись в файл celerybeat-schedule должна быть предотвращена по дизайну. Это отрывок из shelve документации :

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

Предположим, мы начинаем взбивать сельдерей так:

celery -A project-name beat -l info -s /nfs_shared_volume/celerybeat-schedule

где /nfs_shared_volume - это общий том (например, управляемый AWS Elastic File System), можем ли мы ожидать, что расписания не будут испорчены, даже если на каждом узле кластера запущен один процесс сельдерея?

@mikeschaekermann Если я правильно читаю документацию, shelve не предпринимает никаких усилий для предотвращения одновременного доступа на запись. Он просто говорит вам не позволять этому случиться. В процитированном вами разделе говорится: «Для решения этой проблемы можно использовать блокировку файлов Unix, но она отличается в разных версиях Unix и требует знаний об используемой реализации базы данных».

@ ze-phyr-us Я думаю, что вы правы, и я неверно истолковал shelve документы. Тем не менее, мне интересно, будет ли проблема решена, если бэкэнд Scheduler обеспечивает атомарные операции по расписанию? @ask делает django-celery-beat пакета атомарность поддержки для решения этой проблемы? Я видел, что он действительно использует транзакции для выполнения некоторых обновлений.

Для всех, кто попадает сюда в поисках удобного для распределения / автоматического масштабирования стебля сельдерея и счастлив использовать Redis в качестве бэкэнда; Я пробовал как BeatCop, так и single-beat, упомянутые выше, но в конечном итоге выбрал RedBeat .

Привет @ddevlin
У меня похожие проблемы, с какими проблемами вы столкнулись при использовании single-beat? Также, если его не слишком много, не могли бы вы поделиться образцом реализации того, как вы настроили redbeat для нескольких серверов.

@ ankur11 single-beat гарантирует, что работает только один экземпляр сельдерея, но не синхронизирует состояние расписания между экземплярами.

Если бы я использовал планировщик по умолчанию с периодической задачей, предназначенной для запуска каждые 15 минут, и имел бы аварийное переключение с однократным запуском через 14 минут после последнего запуска задачи, задача не запускалась бы до 15 минут после нового удара сельдерея. Экземпляр запущен, в результате чего перерыв в 29 минут.

Чтобы разделить состояние расписания между экземплярами, мне нужно было использовать альтернативный планировщик . django-celery-beat - альтернатива, упомянутая в документации по Celery, но я предпочел использовать Redis в качестве бэкэнда для синхронизации расписания, поскольку я уже использовал Redis в качестве бэкэнда Celery.

Redbeat включает в себя как общее состояние расписания, поддерживаемое Redis, так и блокировку, чтобы гарантировать, что только один экземпляр будет планировать задачи, поэтому мне не понадобились Single-beat или BeatCop, как только я начал его использовать.

В нашей реализации «сельдерейный ритм» запускается супервизором во всех случаях с Redbeat в качестве планировщика (например, exec celery beat --scheduler=redbeat.RedBeatScheduler --app=myproject.celery:app ). К сожалению, я не могу поделиться кодом, связанным с работой, но я рад ответить на любые дополнительные вопросы о реализации в целом.

@mikeschaekermann, вы можете попробовать обернуть свой сельдерей / use / bin / flock, чтобы заблокировать доступ ...

flock /nfs/lock.file celery beat ...

Предполагая, что вы доверяете своей реализации блокировки NFS :)

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

@mikeschaekermann, вы можете попробовать обернуть свой сельдерей / use / bin / flock, чтобы заблокировать доступ ...

flock /nfs/lock.file сельдерей взбить ...

Предполагая, что вы доверяете своей реализации блокировки NFS :)

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

Я пробовал этот метод. К сожалению, если клиент, удерживающий блокировку NFS, теряет связь с сервером NFS, блокировка может быть снята сервером NFS и передана другому клиенту. Когда первоначальный держатель блокировки восстанавливает соединение, flock не понимает, что блокировка была отменена, поэтому теперь есть два узла, которые считают себя «лидером».

В итоге я использовал рекомендательную блокировку в Postgres. Я создал команду управления Django, которая использует модуль django_pglocks и запускает celery beat в подпроцессе.

В итоге я использовал рекомендательную блокировку в Postgres. Я создал команду управления Django, которая использует модуль django_pglocks и запускает celery beat в подпроцессе.

Похоже, что это может быть подвержено тем же проблемам, которые я видел при использовании NFS. Что произойдет, если клиент, удерживающий блокировку, потеряет соединение с сервером Postgres или если сервер Postgres будет перезапущен?

@ swt2c Ага , конечно ты прав! Должен быть какой-то жильё.

Прямо сейчас делаю:

def _pre_exec():
    prctl.set_pdeathsig(signal.SIGTERM)

with advisory_lock(LOCK_ID) as acquired:
            assert acquired
            logging.info("Lock acquired: %s", acquired)
            p = subprocess.Popen(
                celery,
                shell=False,
                close_fds=True,
                preexec_fn=_pre_exec,
            )
            sys.exit(p.wait())

advisor_lock поддерживает рекурсию, но я не знаю, действительно ли она проверяет базу данных:

In [8]:  with advisory_lock('foo') as acquired:
   ...:     print acquired
   ...:     while True:
   ...:        with advisory_lock('foo') as acquired:
   ...:           print acquired
   ...:        time.sleep(1)
   ...:       

# Yes, it does:

True
True
True
<shutdown the instsance>
InterfaceError: cursor already closed

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

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

У меня также есть расписание хранения в БД, но я не тестировал, что делает бит, когда БД падает.

@ddevlin Я был рад увидеть ваш комментарий, так как это было решение, которое я тоже хотел реализовать.

Однако, если бы вы могли поделиться логикой того, как супервизор автоматически перезапускает redbeat-1 когда redbeat-2 выходит из строя, это было бы большим подспорьем.

Это может быть из-за моего непонимания относительно supervisor , но кажется, что autorestart=True эффективен только для программ, которые хотя бы один раз попадают в состояние RUNNING .

Моя проблема:

  1. У меня есть два program в моем supervisor.conf из celery beat с redbeat.RedBeatScheduler .
  2. При запуске супервизор один beat ( beat-1 ) получает блокировку и запускается, в то время как другой ( beat-2 ) пытается запустить пару раз и входит в FATAL state (с ошибкой Seems we're already running? ).
  3. В идеале, если beat-1 останавливается, тогда я хочу, чтобы супервизор запустил beat-2 .
  4. Однако этого не происходит, поскольку он никогда не был в состоянии RUNNING с самого начала. Это означает, что если я остановлю beat-1 , он остановится и ничего не произойдет.

Вне всяких сомнений, решением было бы иметь cron который продолжал бы делать supervisorctl restart all каждые 5 секунд или около того, но просто хотел узнать ваши мысли о том, как вы смогли достичь это дублирование с супервизором.

Привет @harisibrahimkv , ваша проблема в том, что вы ERROR: Pidfile (celerybeat.pid) already exists. для beat-2 ? Я вижу, что наличие двух экземпляров сельдерея, запущенных на одном и том же хосте, было бы полезно для тестирования аварийного переключения между ними, но для реальной избыточности вы, вероятно, захотите, чтобы сельдерей работал на нескольких хостах.

Чтобы запустить несколько экземпляров на одном хосте, попросите супервизора запустить их с аргументом --pidfile и передать им отдельные файлы pid: например

# beat-1 
celery beat --scheduler=redbeat.RedBeatScheduler --pidfile="beat-1.pid" ...
# beat-2
celery beat --scheduler=redbeat.RedBeatScheduler --pidfile="beat-2.pid" ...

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

Наша цель состояла в том, чтобы иметь автоматически масштабируемый пул идентичных хостов, запускающих сельдерей и сельдерей под супервизором. У каждого хоста есть один экземпляр стручка сельдерея. В этой конфигурации сельдерейный бит должен успешно запускаться на всех хостах, но любые экземпляры сельдерея, которые не получают блокировку, будут эффективно использоваться в качестве горячего резерва и не будут планировать задачи (хотя все хосты в пуле будут обрабатывать задачи). Если экземпляр с блокировкой остановлен (например, когда пул уменьшается в масштабе или когда мы выполняем последовательное обновление хостов в пуле), то один из резервных экземпляров получит блокировку и возьмет на себя задачи планирования.

@ddevlin Большое спасибо за то, что вернулись ко мне и сделали Интернет таким замечательным местом! Искренне признателен! (бегал, рассказывая всей моей семье о вашем ответе: D)

  1. Бит pidfile работал, и я был так счастлив видеть, что beat-2 берет на себя задачи, когда другой остановился. Можно настроить время удара с помощью CELERYBEAT_MAX_LOOP_INTERVAL = 25 (на сельдерее 3.x).

  2. Да, для реального резервирования мы планируем установить эту настройку на разных экземплярах в целом. Спасибо, что объяснили настройку, которую вы использовали. Сейчас займемся этим. Настройка «несколько хостов на одном экземпляре», как вы правильно поняли, заключалась только в первоначальной проверке того, работает ли концепция аварийного переключения с этой настройкой супервизора.

Теплое спасибо,
Из маленькой деревушки на самой южной оконечности Индийского субконтинента. :)

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