Celery: 特性:Beat 应该避免并发调用

创建于 2010-11-17  ·  48评论  ·  资料来源: celery/celery

要求用户确保他们的集群中只存在一个 celerybeat 实例会造成巨大的实现负担(要么创建单点故障,要么鼓励用户滚动他们自己的分布式互斥锁)。

celerybeat 应该提供一种机制来防止无意并发,或者文档应该建议一种最佳实践方法。

Celerybeat

最有用的评论

@ankur11 single-beat 确保只有一个 celery beat 实例正在运行,但不会同步实例之间的调度状态。

如果我将默认调度程序与计划每 15 分钟运行一次的定期任务一起使用,并在上次任务运行后 14 分钟进行单节拍故障转移,则该任务将在新 celery 节拍后 15 分钟后运行实例开始,导致 29 分钟的间隔。

为了在实例之间共享调度状态,我需要使用替代调度程序django-celery-beat是 Celery 文档中提到的替代方案,但我更喜欢使用 Redis 作为计划同步的后端,因为我已经使用 Redis 作为我的 Celery 后端。

Redbeat 包括 Redis 支持的共享调度状态和锁定,以确保只有一个实例正在调度任务,所以一旦我开始使用它,我就不需要单节拍或 BeatCop。

在我们的实现中,celery beat 由所有实例的 supervisord 启动,Redbeat 作为调度程序(例如exec celery beat --scheduler=redbeat.RedBeatScheduler --app=myproject.celery:app )。 不幸的是,我无法分享与工作相关的代码,但我很乐意回答有关一般实现的任何其他问题。

所有48条评论

这可以通过使用kombu.pidbox来解决,这也是celeryd检测已经有同名节点在运行的方式。 由于celerybeat是集中式的,它可以使用固定的节点名称。

作为一个副作用,我们将能够使用远程控制命令来控制 celerybeat(例如,可以有一个命令来重新加载时间表,或者查看近期到期的任务)。 如果你问我,那是一个非常棒的副作用。

需要更多规划,因为有在同一个集群中运行多个实例的用例。 例如,用于“分片”多个部分的时间表。 必须至少有可能为每个实例选择一个节点名称。 推迟到 2.3.0。

我们遇到了一个问题,即运行celerybeat的盒子脱机了,而没有一个很好的后备来启动一个新的celerybeat实例来代替它。 运行celerybeat的推荐 HA 方式是什么?

kombu.pidbox方法是否允许我们运行celerybeat多个实例,如果它检测到一个实例已经在以固定节点名称运行并且轮询以将自己提升为活动状态,则该实例只会休眠实例关闭?

运行多个活动实例听起来很有趣——除了共享计划之外还有什么其他好处?

+1

+1

这是大型部署真正关心的事情,因为调度的弹性很重要。

+9999 ;)
使用kombu.pidbox解决方案有什么问题吗? 即使没有分片和花哨的功能,这也会很棒而且非常方便。 现在我需要在另一台主机上手动启动 celerybeat。

可以使用pidbox,但问题是beat不是消费者。 响应广播消息,例如“这里有任何节拍实例?” 它必须不断地侦听广播队列中的消息,而目前不能,因为它正忙于调度消息。

从技术上讲,它可以使用第二个线程,但这可能会降低性能,并且仅针对此功能会产生大量开销。

第二种解决方案可能是使用锁,但缺点是必须释放它。 即,如果 beat 进程被终止,则过时的锁将需要手动干预以启动新实例。

它也可以在锁上有 2 秒的超时时间,并且每秒更新一次锁。 那么这意味着如果持有锁,新实例将不得不等待 2 秒。

可以通过声明队列来创建 amqp 中的锁,例如 `queue_declare('celerybeat.lock', arguments={'x-expires': 2000}``

+1

我很想看到这个

+1

+1

+1 也是

+1

有没有人真正实现了kombu.pidbox解决方案或任何其他解决这个问题的机制? 如果是这样,请分享它。 有很多人仍然想知道什么是最佳实践。

有没有人因此而完全远离芹菜? 我也有兴趣知道。

编辑:

我通过谷歌讨论(https://www.google.co.in/search?q=celerybeat+lock&aq=f&oq=celerybeat+lock&aqs=)找到了这个要点(https://gist.github.com/winhamwr/2719812) chrome.0.57j62l3.2125j0&sourceid=chrome&ie=UTF-8)。

我还想知道是否有人直接将共享的 pidfile 用于 celerybeat,也许是在 AWS 上使用 EBS 或者在 S3 存储桶中...... celerybeat --pidfile=/path/to/shared/volume

我注意到当前的 master (3.1 dev) 有一个消费者的八卦步骤。 是否可以利用八卦队列和领导者选举来协调嵌入的节拍过程? 也就是说,每个工作人员都将运行嵌入式节拍进程,但只有领导者会将周期性任务排队。 这可能会假设共享计划存储。

@mlvin这可以工作,但仅适用于支持广播的代理传输

pidbox 解决方案的问题是必须重写 celerybeat 程序以使用 Async I/O。
由于调度程序正在阻塞,它当前不能同时使用任务并生成它们。

在大多数情况下,这根本不是必需的功能,因为大多数生产部署都有一个用于 beat 进程的专用主机,然后使用--pidfile就足以确保您不会启动多个实例。

我发现受此问题影响的人通常是在守护进程中使用-B选项的人
脚本,然后将该设置复制到另一台主机。

所以我知道这很烦人,但我认为这并不重要。 如果有人真的想要一个解决方案,那么他们可以贡献它,或者雇佣我/捐赠来实现它。

可以使用uWSGI进行单节拍过程并回

+1,我们启动了相同的 Amazon EC2 实例,并且拥有仅在一个节点中执行的周期性任务会很好。 同时我将尝试使用 uWSGI 感谢您的建议。

+1

+1

我一直在工作中使用 Celerybeat 进行调度,但没有开箱即用的 HA 使其变得非常困难。 事实上,看起来我们将因此完全放弃它。 很简单,仅运行 1 个 Celerybeat 实例会使这成为单点故障,因此无法为我们做好生产准备。

@junaidch我认为你不应该因此而放弃芹菜。 您总是可以简单地在每台服务器上运行调度程序,并且对于周期性任务,使用某种锁定机制来确保它们不会以任何方式重叠,也不会运行太频繁。 此外,您可以子类调度程序并在那里做一个锁,或者跳过任务级锁而只在调度程序中执行所有操作。

在 celery 中拥有一些内置功能会更好,因为这是一种解决方法,但它仍然可以在生产中使用。

谢谢@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重定向为父母,如果孩子用相同的代码死亡,则退出,配置它环境变量。 所以添加到主管更容易一些。

希望它可能对其他人有用。

+1

+1

我正在考虑使用 Consul 键值作为锁控制器,有人尝试过这种方法吗? 因此,当有一个实例工作时,其他实例将“休眠”直到锁没有更新,然后 Consul 选举机制将决定谁将更新带时间戳的键值。 更新锁的人就是工作的人。

@ingmar谢谢你! 我将在我的工作集群上试一试。

+10 作为当前实现意味着单点故障,这与我们首先使用分布式队列的原因相去甚远

+1

+1

完成这项工作,与目前的资源一样,需要 10 年时间才能完成。

抱歉,这对于所谓的“分布式”队列来说是一个严重的问题。 无论这需要多长时间来实施,它最终应该被修复。 关闭一个完全有效的问题,因为您现在没有资源 _right now_ 似乎不正确。 您能否重新打开它并应用一个表明它目前为低优先级的标签?

我知道我关闭的原因非常突然,所以作为软件用户我可以理解您的感受,但从技术上讲,Beat 更像是一个附加功能。 它与 Celery 的其余部分完全分离,并且有意设计为非分布式以保持实现简单。 它最初是从 Python 定义 cronjobs 的一种巧妙方式,作为已经使用 Celery 的用户的奖励,然后越来越多的人使用 Celery 作为 cron 替代品。

这个问题已经公开了六年,尽管它经常被要求,无数公司依赖它,但没有人愿意为它的实施支付费用。

这实际上是我认为公司会感兴趣的问题之一。 诚然,公司愿意为任何功能、错误修复甚至帮助解决生产问题付费,这并不常见。 我大概可以用一只手数出来(你太棒了),所以现在我知道这个想法是多么天真:)

我今天也关闭了此问题的副本,请参阅 #1495。 已经有拉取请求试图解决这个问题,并且有几个是有希望的,但是考虑到证明给定实现有效所需的奉献精神,我仍然没有时间正确审查它们。 也许这会促使某人采取行动,即使没有,我认为这比在没有人处理它的情况下将功能请求保持开放六年要好。 对于希望看到此修复的用户来说,这也是一种伤害。

@ask足够公平。 确实,分布式 cron 是一个很大的复杂问题,就像您在另一个线程中所说的那样。 它听起来确实应该存在于 Celery 之外。

感谢您花时间详细解释您的推理。

@ask我想知道是否可以通过将celerybeat-schedule文件(由celery.beat.PersistentScheduler )定位在跨集群中所有节点共享的 NFS 卷中来规避这个问题?

PersistentScheduler类使用shelve作为数据库模块,因此设计上应该防止并发写入celerybeat-schedule文件。 这是shelve文档的摘录:

搁置模块不支持对搁置对象的并发读/写访问。 (多个同时进行的读访问是安全的。)当一个程序有一个为写而打开的架子时,没有其他程序应该为读或写而打开它。

假设我们像这样开始 celery beat:

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

其中/nfs_shared_volume是共享卷(例如,由 AWS Elastic File System 管理),我们是否可以期望即使在集群中的每个节点上都运行一个 celery beat 进程,计划也不会混乱?

@mikeschaekermann如果我正确阅读文档shelve不会阻止并发写入访问。 它只是告诉你不要让它发生。 您引用的部分继续说“可以使用 Unix 文件锁定来解决这个问题,但这在 Unix 版本之间有所不同,并且需要了解所使用的数据库实现。”

@ze-phyr-us 我认为你是对的,我误解了shelve文档。 不过,我想知道假设Scheduler后端确保按计划进行原子操作,问题是否会得到解决? @ask django-celery-beat包是否支持原子性来解决问题? 我看到它确实使用事务来进行一些更新。

对于在搜索分布式/自动缩放友好 celery beat 时最终到达这里并乐于使用 Redis 作为后端的任何其他人; 我尝试了上面提到的 BeatCop 和单节拍,但最终选择了RedBeat

嗨@ddevlin
我遇到了类似的问题,您在使用单节拍时遇到了什么问题? 另外,如果不是太多,请您分享如何为多个服务器配置 redbeat 的实现示例。

@ankur11 single-beat 确保只有一个 celery beat 实例正在运行,但不会同步实例之间的调度状态。

如果我将默认调度程序与计划每 15 分钟运行一次的定期任务一起使用,并在上次任务运行后 14 分钟进行单节拍故障转移,则该任务将在新 celery 节拍后 15 分钟后运行实例开始,导致 29 分钟的间隔。

为了在实例之间共享调度状态,我需要使用替代调度程序django-celery-beat是 Celery 文档中提到的替代方案,但我更喜欢使用 Redis 作为计划同步的后端,因为我已经使用 Redis 作为我的 Celery 后端。

Redbeat 包括 Redis 支持的共享调度状态和锁定,以确保只有一个实例正在调度任务,所以一旦我开始使用它,我就不需要单节拍或 BeatCop。

在我们的实现中,celery beat 由所有实例的 supervisord 启动,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 celery beat ...

假设您信任您的 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

所以......我可以修改它以继续重新获取锁定/轮询并在失败时杀死beat。 不保证互斥,但它可能足以满足我的目的。

对于我的情况,并发节拍是一种浪费性的烦恼,但不是完整性问题。 如果是这样,我也可以将任务包装在一个咨询锁中,如果数据库出现故障,任务无论如何都会失败。

我也将节拍存储在数据库中,但还没有测试节拍在数据库出现故障时会做什么。

@ddevlin我很高兴看到您的评论,因为这也是我正在考虑实施的解决方案。

但是,如果您可以分享当redbeat-2出现故障时主管如何自动重新启动redbeat-1的逻辑,那将是一个很大的帮助。

这可能是由于我对supervisor缺乏了解,但似乎autorestart=True仅对至少进入RUNNING状态一次的程序有效。

我的问题是:

  1. 我在program的 supervisor.conf 中有两个celery beatredbeat.RedBeatScheduler
  2. 启动主管,一个beat ( beat-1 ) 获得锁并运行,而另一个 ( beat-2 ) 尝试启动几次并进入FATAL状态(带有Seems we're already running?错误)。
  3. 理想情况下,如果beat-1停止,那么我希望主管启动beat-2
  4. 但是,这不会发生,因为它从一开始就从未处于RUNNING状态。 这意味着如果我停止beat-1 ,那么它就会停止,然后什么也没有发生。

在我的脑海中,解决方案是让cron supervisorctl restart all每 5 秒左右不断执行

@harisibrahimkv ,您的问题是您在同一主机上启动了两个相同的 celery beat 实例; 我希望您在beat-2日志中看到ERROR: Pidfile (celerybeat.pid) already exists. beat-2 ? 我可以看到在同一主机上运行两个 celery beat 实例对于测试它们之间的故障转移很有用,但对于真正的冗余,您可能希望 celery beat 在多个主机上运行。

要在同一主机上运行多个实例,让主管用--pidfile参数启动它们,并为它们提供单独的 pidfiles:例如

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

两个实例都应该在主管下成功启动,但是如果您检查日志文件,则只有其中一个应该是调度任务。 如果您停止该实例,您应该会看到另一个实例接管任务调度。

我们的目标是拥有一个由相同主机组成的自动扩展池,运行 celery 工人,并在主管下运行 celery。 每个主机都有一个 celery beat 实例。 在此配置中,celery beat 应该在所有主机上成功启动,但任何未获取锁的 celery beat 实例将有效地成为热备用而不是调度任务(尽管池中的所有主机都将处理任务)。 如果带有锁的实例停止(例如,当池缩减时,或者当我们对池中的主机进行滚动升级时),那么其中一个备用实例将获取锁并接管调度任务。

@ddevlin 非常感谢您回复我并使互联网成为一个如此美好的地方! 衷心感谢! (跑来跑去告诉我全家人你的回复:D)

  1. pidfile位起作用了,我很高兴看到beat-2在另一个停止时承担任务。 可以用CELERYBEAT_MAX_LOOP_INTERVAL = 25配置节拍时间(在 celery 3.x 上)。

  2. 是的,为了真正的冗余,我们计划完全在不同的实例上进行此设置。 感谢您解释您使用的设置。 现在就开始做这件事。 正如您正确理解的那样,“同一实例上的多个主机”设置只是最初验证故障转移的概念是否适用于此主管设置。

热烈感谢,
来自印度次大陆最南端的一个小村庄。 :)

此页面是否有帮助?
0 / 5 - 0 等级