celery常见问题

以下说法都是反面教材

1.入列数据而不是引用

如果将数据库中的数据直接传递给任务参数,而不是通过引用该条数据的ID或者其他唯一标识,那么在这个任务执行之前,这些数据可能已经过期/变更了。

例如,假设你在任务参数中使用用户的电子邮件地址,而这个任务要延迟1分钟运行。如果用户在该任务运行之前更改了他的电子邮件地址,那么电子邮件将被发送到错误的地址上。

2. 在数据库事务中入列任务

虽然Celery被认为是用来“稍后/最终”执行任务的,但是它也可以很快地执行任务!如果你从数据库事务中入列一个任务,那么它可能会在数据库提交事务之前执行。这可能意味着你的任务需要访问的数据是不可见的(例如一个新的模型实例)。解决方案在事务提交后入列你的任务。如果你没有在你的视图中使用事务,那么你也可以在任务所需要的所有数据提交之后再执行任务。

另外从数据库副本读取数据的任务也会出现数据过期/未同步的问题。建议解决方案是在模型上通过一个updated_at 字段进行筛选,以确保你得到一个最新的时间戳。如果你使用的是MySQL,你也可以通过检查全局事务ID是否被应用到数据库副本上来实现。

3.不在任务中使用数据库事务

虽然Django使在视图中通过ATOMIC_REQUESTS使用数据库事务变得容易,但是对于views的代码,你就得靠自己了。这包括Celery任务。如果你没有使用transaction.atomic()包装你的任务,或者在你的任务体中使用它,那么你可能会遇到数据完整性问题。为了找到应该在哪里使用transaction.atomic(),审查一下你的任务是值得的。你甚至可以为Celery的@shared_task添加一个特定于项目的包装器,它可以将@atomic添加到你的任务中。

4. 默认的“不公平的”任务分配

默认情况下,Celery worker会将任务成批地发送到其worker进程,这些任务在worker进程中会再一次排队,等待被执行。这样做是为了提高吞吐量,因为worker进程不需要等待来自broker的任务。但这确实意味着一个进程中运行时间较长的任务会拦截排在它后面的速度更快的任务,即使其他工作进程有空闲去运行它们。泰勒·休斯的《Celery小贴士》一文的技巧#2中有一个很棒的图表直观地展示了这一点。根据我的经验,这种默认行为从来就是不可取的。项目都会具有运行时间差异很大的任务,这是很常见的,并且也会导致这种默认行为出现阻塞。你可以通过使用-O fair运行celery worker来禁用它。Celery文档中关于“Prefork池预存取设置”的部分对此有一个更好的解释。

5. 给任务指定了一个很长的countdown或一个eta

Celery为任务队列提供了eta和countdown参数。这些参数允许你对任务进行计划以便稍后执行。不幸的是,这些工作方式并没有被内置到代理中。这些延迟的任务最终会排在队列的最前面,排在后面的非延迟任务之前。Celery工作进程会获取延迟的任务并在内存中将它们“放在一边”,然后再获取非延迟的任务。如果这样的任务过多,Celery工作进程将使用大量内存来保存这些任务。重新启动工作进程还需要重新获取队列头部的所有延迟的任务。我见过这样的情况,其中有大量的延迟任务,重新启动花费了几分钟才开始做实际的工作!如果你需要延迟任务超过几分钟,你应该避免eta和countdown。最好是向你的模型实例中添加一个字段,并指定要执行的时间,然后使用一个定期任务对实例执行即时入列。

6. ACKS行为Celery的默认行为是立即确认任务,不管是否完成了任务,同时从你的代理队列中将它们删除。如果它们被中断,例如被一个随机的服务器崩溃中断,那么Celery将不会重试这个任务。如果你的任务不是幂等的(可重复而不会出问题),这种行为是很好的。但它不适用于处理随机错误,比如你的数据库连接随机断开。在这种情况下,你的工作就会丢失,因为Celery在尝试它之前就把它从队列中删除了。相反的行为,“延迟确认”,只在任务成功完成后进行确认。这是其他许多队列系统(如SQS)所推荐的行为。Celery在它的FAQ“我应该使用重试还是acks_late?”中对这一点进行了介绍。这是一个微妙的问题,但我确实认为默认的“提前确认”行为是违反直觉的。我建议你在Celery配置中将acks_late = True设置为默认值,并充分考虑哪种模式适合每个任务。你可以通过将acks_late传递给@shared_task装饰器来在每个任务函数上重新配置它。

6. 不重试漏掉的任务

任务可能会因为各种各样的原因而崩溃,而其中的许多任务是你无法控制的。例如,如果你的数据库服务器崩溃了,Celery可能就无法执行任务,并且会引发一个“连接失败”错误。解决这个问题最简单的方法是使用第二个定期的“清理器任务”,它将扫描并重复/重新入列漏掉的任务。AWS最近的一篇文章《避免难以解决的队列积压》中指出,他们把这类任务称为“逆熵清理器”。(那篇文章读起来棒极了,里面满是关于规模化处理队列的建议。)

7. 以向后不兼容的方式更改任务签名

假设我们有这样一个任务函数,我们对它进行更改,增加一个新的必选参数,当我们部署这一改变时,任何来自第一个版本代码中的入列任务都将因错误(missing 1 arg)而失败。你应该像对待数据库迁移一样来考虑任务函数签名。如果你要添加一个新参数,首先给它一个默认值——类似于为数据库添加可为空的列。当该版本的代码运行后,你就可以删除默认值。类似地,如果你要删除一个参数,你首先得给它一个默认值。如果你要删除一个任务,首先删除调用位置,然后再删除任务本身。我不知道有什么工具可以帮助我们实现这一点,尽管在YPlan的前一份工作中,我们对Django系统进行了一些内部检查。这确保我们仔细考虑了我们对任务和它们的参数所做的每一个改变。


celery常见问题
http://shize.store/技术/celery常见问题/
作者
Ziggy
发布于
2022年8月5日
许可协议