Django模型实例应该传递给Celery吗?

25 投票
2 回答
11472 浏览
提问于 2025-04-17 17:07
# models.py
from django.db import models

class Person(models.Model):
    first_name = models.CharField(max_length=30)
    last_name = models.CharField(max_length=30)
    text_blob = models.CharField(max_length=50000)

# tasks.py
import celery
@celery.task
def my_task(person):
    # example operation: does something to person 
    # needs only a few of the attributes of person
    # and not the entire bulky record
    person.first_name = person.first_name.title()
    person.last_name = person.last_name.title()
    person.save()

在我的应用程序中,有一些类似这样的内容:

from models import Person
from tasks import my_task
import celery
g = celery.group([my_task.s(p) for p in Person.objects.all()])
g.apply_async()
  • Celery会把数据打包成“泡菜”(pickles)然后发送给工作者,对吧?
  • 如果工作者在多台机器上运行,整个“人”对象(还有那些大块的文本数据,其实并不需要)会通过网络传输吗?有没有办法避免这种情况?
  • 我该如何有效且均匀地把“人”的记录分配给在多台机器上运行的工作者呢?

  • 这样做会不会更好?如果“人”的记录有几百万条,数据库会不会承受不住?

    # tasks.py
    
    import celery
    from models import Person
    @celery.task
    def my_task(person_pk):
        # example operation that does not need text_blob
        person = Person.objects.get(pk=person_pk)
        person.first_name = person.first_name.title()
        person.last_name = person.last_name.title()
        person.save()
    
    
    #In my application somewhere
    from models import Person
    from tasks import my_task
    import celery
    g = celery.group([my_task.s(p.pk) for p in Person.objects.all()])
    g.apply_async()
    

2 个回答

2

是的。如果数据库里有几百万条记录,那这个方法可能不是最好的选择。不过既然你需要处理这么多记录,不管你用什么方法,数据库都会受到很大的压力。

这里有一些其他的选择,虽然我不认为哪个更好,只是不同的做法。

  1. 为你的Person类实现一个预保存信号处理器,这样可以自动处理名字的格式问题。这样一来,你的名字在数据库里就会正确存储,以后就不需要再做这个了。
  2. 使用一个管理命令,接受某种分页参数……比如可以用姓的首字母来分组处理这些人。这样运行 ./manage.py my_task a 就会更新所有姓氏以“a”开头的记录。显然,你需要多次运行这个命令才能处理完整个数据库。
  3. 也许你可以用一些创意的SQL语句来实现。我这里就不尝试了,但值得去研究一下。

记住,.save() 方法对数据库的影响会比实际选择几百万条记录要大得多。

18

我觉得传递主键(PK)比传递整个模型对象要更好也更安全。因为主键只是一个数字,所以序列化(把数据转换成可以存储或传输的格式)也简单多了。最重要的是,你可以使用更安全的序列化方式,比如用json或yaml,而不是pickle,这样你就不用担心在序列化模型时会遇到问题。

正如这篇文章所说:

由于Celery是一个分布式系统,你无法知道任务会在哪个进程中运行,甚至在哪台机器上。因此,你不应该把Django模型对象作为任务的参数,通常从数据库重新获取对象会更好,因为这样可以避免可能出现的竞争条件。

撰写回答