Celery+Django: 异步邮箱验证

Posted by Borg on May 8, 2017

其实。。。这篇教程不包括邮箱验证的,不过我有实现个 celery + django 的邮箱验证博客,问末附 repo 。

Web 应用中的长时操作如果没有异步实现会阻塞代码运行,用户需要等待较长时间才能收到响应。而像 Celery 这样的异步工具就能很好解决这类问题。本文将带你了解 Django 框架下的 Celery 使用。

安装

Celery

Celery 使用 pip 安装即可。

pip install celery

Celery 还需要 broker,貌似有翻译成中间人的,类似个消息队列。这里将使用 Redis 作为 broker。redis 安装如下:

wget http://download.redis.io/releases/redis-3.2.8.tar.gz
tar xzf redis-3.2.8.tar.gz
cd redis-3.2.8
make
make test

此时 src 文件夹下应该就有了 redis-server 和 redis-cli 二进制文件,可以把它们复制到 /usr/local/bin 目录下方便以后使用。此处暂不复制,启动 redis 服务器,使用默认端口 6379 。

redis-server

保持 redis 服务开启,生产环境下建议参考 redis 入门指南 进行 redis 配置。

Django

Celery 4 支持 Django 1.8 以上版本,已经不需要安装其它包。

pip install django=1.9

Django 项目

启动一个 Django 项目,并创建一个应用名为 Project ,创建一个应用叫 celeryExample。

django-admin startproject Project
cd Project
python manage.py startapp celeryExample

在 Project/Project 文件夹下创建一个文件用于放置 celery 启动的代码,名为 celery.py,在 Project/celeryExample 文件夹下创建一个文件用于放置具体的 celery task,名为 tasks.py。则文件结构应该如下:

  • Project
    • Project
      • init.py
      • celery.py
    • celeryExample
      • init.py
      • tasks.py

为简洁,以上省去了 Django 自动创建的文件。
把 celeryExample 添加到 Django INSTALLED_APPS 里。添加后 Project/Projcet/settings.py 中的 INSTALLED_APPS 应该如下:

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'celeryExample'
]

Celery App

Django 环境下启动 Celery 官方文档有相应代码,直接复制到 Project/Project/celery.py 文件内,稍加修改。此处将 ‘os.environ.setdefault` 第二个参数修改为“Project.settings”,将 celery 应用名称改为了 Project,在构造函数里传入 broker 参数设置使用 redis,并且删除了 debug_task 方法,最后代码如下,可直接复制。

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

# set the default Django settings module for the 'celery' program.
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'Project.settings')

app = Celery('Project', broker="redis://localhost")

# Using a string here means the worker don't have to serialize
# the configuration object to child processes.
# - namespace='CELERY' means all celery-related configuration keys
#   should have a `CELERY_` prefix.
app.config_from_object('django.conf:settings', namespace='CELERY')

# Load task modules from all registered Django app configs.
app.autodiscover_tasks()

上面的代码实例化了个 celery app,borker 参数可以设置使用的中间人,也不一定要使用redis,可以使用其它数据库。app.autodiscover_tasks() 能够自动在所有的 Django app 里查找 task,能够实现 celery app 和 task 的分离,更符合大型项目的结构。为保证 celery 能够在 Django 启动时启动,把 celery.py 里的 app import 到 Project/Project/init.py ,如下:

from .celery import app as celery_app

Celery Tasks

Celery task 为具体的异步操作函数。在 Project/celeryExample/tasks.py 文件内进行定义。

from celery import shared_task
import time

@shared_task
def long_time_operation():
    time.sleep(10)
    return True

此处使用了 shared_task 装饰器,用于设置不与具体 celery 应用绑定的 task。使用time.sleep(10) 来模拟长时操作。

有了 task 就可以使用它了,我们需要分别创建两个页面,一个使用异步操作,另一个则不使用异步操作进行比较。在 Project/celeryExample/views.py 里添加视图函数:

from celeryExample.tasks import long_time_operation
from django.http import HttpResponse
# Create your views here.

def async_op(request):
    long_time_operation.delay()
    return HttpResponse("Async Op!")

def block_op(request):
    long_time_operation()
    return HttpResponse("Finished!")

注意直接调用 long_time_operation 不会交给 celery 异步处理,需要使用 `long_time_operation.delay()’ 才可以。

为视图函数添加相应的 url。修改 Project/Project/urls.py 如下:

from django.conf.urls import url, include
from django.contrib import admin

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r"^celery/", include("celeryExample.urls", namespace="celery"))
]

新建 Project/celeryExample/urls.py 如下:

from django.conf.urls import url
import celeryExample.views as views

urlpatterns = [
    url(r"async", views.async_op),
    url(r"block", views.block_op)
]

启动应用

首先启动 celery,在第一层 Project 文件夹下(与 manage.py 同一目录下)启动终端输入以下代码启动 celery。

celery -A Project worker --loglevel=info

接着可以启动 Django server 了,新开启一个终端输入:

python manage.py runserver

测试

此时就一切就绪了,首先访问http://127.0.0.1:8000/celery/block,浏览器将会一只处于加载状态,直到10秒后长时操作完成才加载结束返回“Finished!”。再访问http://127.0.0.1:8000/celery/async,由于使用了异步处理,浏览器立即就得到了响应,并且在运行 celery 的终端上有相应的输出如下:

[2017-05-08 15:49:41,244: INFO/MainProcess] Received task: celeryExample.tasks.long_time_operation[5ac8d68c-8c89-4c45-b779-66d12d285c02]  
[2017-05-08 15:49:51,251: INFO/PoolWorker-2] Task celeryExample.tasks.long_time_operation[5ac8d68c-8c89-4c45-b779-66d12d285c02] succeeded in 10.003452520999417s: True

是不是差异巨大!BTW: 我在我的博客系统的注册页面上使用了 celery 进行验证邮箱的发送,欢迎围观github项目,实际部署的博客地址,也欢迎 Pull Request!