如何使用Django和Celery

346 阅读8分钟

开始使用Django和Celery

当我们在处理数据密集型的应用程序时,长期运行的任务会减慢应用程序和网站的加载时间。在这样的应用中,我们可以通过将一些工作从应用服务器卸载到消息代理服务器来改善应用的加载时间。

在本教程中,我们将学习如何在Django应用程序中使用Celery来执行长期运行的后台任务。

前提条件

为了继续学习本教程--你将需要以下条件。

  1. 在你的电脑中安装[Python]。
  2. 对[Python]有良好的理解。
  3. 在你的电脑上安装[Docker]。

工人

基于后台的任务服务器被称为workers 。在一个有网络服务器的应用程序中,我们可以让几个工作者在后台执行繁重的计算,并通过webhooks或回调将响应送回应用程序。

Celery消息队列

队列是一种基于先进先出原则工作的数据结构。我们通过消息队列将工作分配给工作者。工作者按照消息代理排队的顺序来处理任务。

队列确保每个工作者一次处理一个任务,而且只有一个工作者处理一个特定的任务。

Celery

Celery使Django应用程序中许多工作者的任务队列的实现变得更加容易。

Celery的功能。

  • 将任务定义为python函数。
  • 听取消息代理的新任务。
  • 将任务分配给工作者。
  • 监控工作者和任务。

项目设置

  1. 通过执行下面的命令创建一个工作目录。
mkdir project
  1. 通过执行下面的命令,将工作目录改为上面创建的project 目录。
cd project
  1. 用下面的命令创建一个Django应用程序。
django-admin startproject celerytask
  1. 用下面的命令创建一个安装软件包的虚拟环境。
virtualenv venv
  1. 通过执行下面的命令激活这个虚拟环境。
source venv/bin/activate
  1. 通过执行下面的命令将Django安装到我们在上面创建的虚拟环境中。
pip install django
  1. 通过执行下面的命令来迁移数据库模型。
cd celerytask
python manage.py migrate
  1. 现在我们可以将芹菜添加到我们的应用程序中。
pip install celery
  1. 通过执行下面的命令,启动Django网络服务器。
python manage.py runserver
  1. 导航到http://localhost:8000/,确认应用程序已经启动并运行。

向Django应用程序添加Celery配置

  • 在存在settings.py 文件的项目文件夹中,创建一个名为celery.py 的新Python文件。

  • 将下面的代码片段添加到上面创建的文件中。

    from __future__ import absolute_import, unicode_literals
    import os
    from celery import Celery

    # setting the Django settings module.
    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'celery_task.settings')
    app = Celery('celery_task')
    app.config_from_object('django.conf:settings', namespace='CELERY')

    # Looks up for task modules in Django applications and loads them
    app.autodiscover_tasks()

上述配置使用Django设置创建了一个Celery应用程序。app.autodiscover_tasks() ,试图在我们所有的Django应用程序中发现一个名为task.py 的文件。

settings.py 文件所在的包内的__init__.py 文件中,添加下面的代码片断。

from .celery import app as celery_app

__all__ = ['celery_app']

上述代码段在我们的应用程序每次启动时导入Celery。

创建一个Celery任务

让我们创建一个Django应用程序,从那里我们将设置Celery任务。

  • 要创建一个新的Django应用程序,执行下面的命令。在该命令中,task 将是我们应用程序的名称。
python manage.py startapp task
  • 在我们刚刚创建的task 目录下,创建一个名为task.py 的Python文件。

  • 将下面的代码片断添加到上面创建的task.py

   import string
   from django.contrib.auth.models import User
   from django.utils.crypto import get_random_string

   from celery import shared_task

   @shared_task
   def create_random_user_accounts(total):
      for i in range(total):
         username = 'user_{}'.format(get_random_string(10, string.ascii_letters))
         email = '{}@example.com'.format(username)
         password = get_random_string(50)
         User.objects.create_user(username=username, email=email, password=password)
      return '{} random users created with success!'.format (total)
   

上面的函数创建随机的用户账户。

Celery任务的方法签名如下所示。

from celery import shared_task

@shared_task
def name_of_your_function(optional_param):
    pass  # do some long running task

创建HTML文件

在项目的根目录下创建一个名为templates 的文件夹。在上面创建的templates 目录中,在其中创建一个名为task 的目录,因为它将存放我们的Djangotask 的HTML文件。

  • 在上面创建的task, 目录中,创建一个名为base.html 的文件,并将下面的代码片段添加到其中。下面的代码片断是基础模板,其他模板文件将从该模板扩展。

    <!DOCTYPE html>
    <html>
    <head>
       <meta charset="utf-8">
       <title>Celery tasks</title>
       <style type="text/css">
          body {
          width: 800px;
          margin: 20px auto;
          }
       </style>
    </head>
    <body>
       <h1>Django Celery Task</h1>
       <a href="{% url 'users_list' %}">Users List</a> /
       <a href="{% url 'generate' %}">Generate Random Users</a>
       <hr>
    
       {% if messages %}
          {% for message in messages %}
             <p style="color: green">{{ message }}</p>
          {% endfor %}
       <hr>
       {% endif %}
    
    {% block content %}
    {% endblock %}
    
    </body>
    </html>
    
  • task 目录中,创建一个名为user_list.html 的文件,并添加下面的代码片段。下面的代码片断将显示生成的随机用户列表。

   {% extends 'task/base.html' %}

   {% block content %}
    <h2>Users List</h2>

    <ul>
        {% for user in object_list %}
            <li>{{ user.username }} - {{ user.email }} - {{ user.date_joined }}</li>
        {% empty %}
            <li>No users. <a href="{% url 'generate' %}">Generate some random users.</a></li>
        {% endfor %}
    </ul>
   {% endblock %}
  • task 目录中,创建一个名为generate_random_user.html 的文件,并添加下面的代码片断。下面的代码片段包含一个输入字段,我们将在这里指定要生成的随机用户的数量。
   {% extends 'task/base.html' %}

   {% block content %}
    <h2>Generate Random Users</h2>
    <form method="post">
        {% csrf_token %}
        <table>
            {{ form }}
        </table>
        <button type="submit">Submit</button>
    </form>
   {% endblock %}

表格

task 目录中(不是模板目录中的那个),创建一个名为form.py 的Python文件并添加下面的代码片段。

from django import forms
from django.core.validators import MinValueValidator, MaxValueValidator

class GenerateRandomUserForm(forms.Form):
    total = forms.IntegerField(
        validators=[
            MinValueValidator(50),
            MaxValueValidator(500)
        ]
    )

查看

将下面的代码片断添加到taskviews.py 文件中。

from django.contrib.auth.models import User
from django.contrib import messages
from django.views.generic import ListView
from django.views.generic.edit import FormView
from django.shortcuts import redirect

from .form import GenerateRandomUserForm
from .task import create_random_user_accounts

# returns a list of generated user accounts
class UsersListView(ListView):
    template_name = 'task/user_list.html'
    model = User

# A page with the form where we can input the number of accounts to generate
class GenerateRandomUserView(FormView):
    template_name = 'task/generate_random_user.html'
    form_class = GenerateRandomUserForm

    def form_valid(self, form):
        total = form.cleaned_data.get('total')
        create_random_user_accounts.delay(total)
        messages.success(self.request, 'We are generating your random users! Wait a moment and refresh this page.')
        return redirect('users_list')

在上面的代码片段中,注意我们没有调用create_random_user_accounts 方法,而是调用了create_random_user_accounts.delay(total) 。这指示Celery作为一个后台进程执行任务。

URLs

用下面的代码片断更新urls.py

from django.conf.urls import url
from django.contrib import admin
from django.urls import path

from task.views import GenerateRandomUserView, UsersListView

urlpatterns = [
    path('admin/', admin.site.urls),
    url('users/', UsersListView.as_view(), name='users_list'),
    url('generate/', GenerateRandomUserView.as_view(), name='generate')

]

设置基础模板目录

settings.py 文件中,用下面的代码片断更新TEMPLATES 部分。

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [BASE_DIR / 'templates'], # We are adding in the directory where our templates will be stored.
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]
  • 执行下面的命令,创建一个requirements.txt 文件,我们将在构建Docker镜像时使用。
pip freeze > requirements.txt
  • requirements.txt 该文件包含我们的应用程序所需的所有软件包。

容器化

创建Docker文件

在项目根目录下,project/celerytask ,创建一个名为Dockerfile 的文件,并添加以下代码片段。

# pull the official base image
FROM python:3.9.5-alpine

# set work directory
WORKDIR /usr/src/app

# set environment variables
ENV PYTHONDONTWRITEBYTECODE 1
ENV PYTHONUNBUFFERED 1

# install dependencies
RUN pip install --upgrade pip
COPY ./requirements.txt /usr/src/app/requirements.txt
RUN pip install -r requirements.txt

# copy project
COPY . /usr/src/app/

RUN python manage.py migrate

EXPOSE 8000

CMD ["python", "manage.py", "runserver", "0.0.0.0:8000"]

上面的代码片段包含了将用于创建Docker镜像的指令。

创建一个Docker compose部署文件

在工作目录project ,创建一个名为docker-compose.yml 的文件,并添加以下代码片段。

version: '3.3'

services:
  web:
    build: ./celerytask #path to the root project folder
    command: python manage.py runserver 0.0.0.0:8000
    volumes:
      - ./celerytask:/usr/src/app/
    ports:
      - 8000:8000 # sets the port that maps to internal port in docker container
    environment:
      - DEBUG=1
      - SECRET_KEY=dbaa1_i7%*3r9-=z-+_mz4r-!qeed@(-a_r(g@k8jo8y3r27%m
      - DJANGO_ALLOWED_HOSTS=localhost 127.0.0.1 [::1]
      - CELERY_BROKER=redis://redis:6379/0
      - CELERY_BACKEND=redis://redis:6379/0
    depends_on:
      - redis

  celery:
    build: ./celerytask
    command: celery worker --app=core --loglevel=info --logfile=logs/celery.log # Command used to start the Celery worker in the Docker container
    volumes:
      - ./celerytask:/usr/src/app
    environment:
      - DEBUG=1
      - SECRET_KEY=dbaa1_i7%*3r9-=z-+_mz4r-!qeed@(-a_r(g@k8jo8y3r27%m
      - DJANGO_ALLOWED_HOSTS=localhost 127.0.0.1 [::1]
      - CELERY_BROKER=redis://redis:6379/0
      - CELERY_BACKEND=redis://redis:6379/0
      # depends on show that celery worker service requires the web service and the redis service to run
    depends_on: 
      - web
      - redis

  redis:
    image: redis:6-alpine

在上面的docker-compose.yml 文件中,我们有3个服务。

  1. web - 是运行我们应用程序代码的服务。
  2. celery- 是运行Celery工作者的服务。
  3. redis - 是运行Redis服务器的服务。Redis是一个密钥对数据存储,将被用来存储排队的事件。

构建镜像

在项目根目录下,执行下面的命令来创建一个镜像。

docker build -t celerytask .
  • -t 为将要创建的镜像添加一个标签 。celerytask
  • . 命令的结尾表示Dockerfile在当前目录下,该命令正在执行。

为了验证镜像是否已经成功创建,请执行下面的命令。

karen@karen:~$ docker image ls
REPOSITORY                                                 TAG                 IMAGE ID       CREATED              SIZE
celerytask                                                 latest              a9803f267258   About a minute ago   172MB

使用Docker compose进行部署

执行下面的命令,创建并启动我们在docker-compose.yml 文件中声明的服务。

docker-compose up -d --build

测试

Celery generate user

  • 点击生成用户后,Celery会安排一个后台任务,在后台生成随机的用户账户,如下图所示。

Celery task scheduled

  • 几秒钟后刷新users ,我们看到一个随机生成的用户列表,如下图所示。

Celery random users

请确保在启动celery工作者之前运行迁移。

总结

现在你已经学会了如何将Celery集成到Django应用程序中并执行定期任务,创建一个Django应用程序,每隔1小时运行一个备份脚本来备份自己。