Django 進階之 celery

weixin_34402408發表於2018-08-27

Django 整合 Celery 到專案:本節將 celery 整合到 Django 專案中,實現非同步任務處理和定時任務處理。

Celery工作流程

7209287-4a90be89a6377f60

Celery 的架構由三部分組成,訊息中介軟體(message broker),任務執行單元(worker)和任務執行結果儲存(task result store)組成。

訊息中介軟體

Celery 本身不提供訊息服務,但是可以方便的和第三方提供的訊息中介軟體整合。包括,RabbitMQ, Redis, MongoDB (experimental), Amazon SQS (experimental),CouchDB (experimental), SQLAlchemy (experimental),Django ORM (experimental), IronMQ。

任務執行單元

Worker 是 Celery 提供的任務執行的單元,worker 併發的執行在分散式的系統節點中。

任務結果儲存

Task result store 用來儲存 Worker 執行的任務的結果,Celery支援以不同方式儲存任務的結果,包括 AMQP, Redis,memcached, MongoDB,SQLAlchemy, Django ORM,Apache Cassandra, IronCache

1、Celery 安裝與配置

在虛擬環境中安裝:

pip install django-celery==3.2.2

pip install django-redis

pip install flower # celery 的 web 管理平臺(非同步任務視覺化)

檢視整合到 Django 中的 celery 版本, pip freeze

celery==3.1.26.post2 django-celery==3.2.2 flower==0.9.2

啟動 redis 服務, 埠假設為 6379

發現 pip 安裝比較慢的情況

pip install pillow -i https://pypi.doubanio.com/simple/

2、Django 中配置
(1)在主工程的配置檔案 settings.py 中應用登錄檔 INSTALLED_APPS 中加入 djcelery
INSTALLED_APPS = [
   'django.contrib.admin',
   'django.contrib.auth',
   'django.contrib.contenttypes',
   'django.contrib.sessions',
   'django.contrib.messages',
   'django.contrib.staticfiles',
   'art',
   'xadmin',
   'crispy_forms',
   'DjangoUeditor',
   'djcelery',       #加入djcelery
]

(2)在 settings.py 中加入 celery 配置資訊

#############################
# celery 配置資訊 start
#############################
import djcelery
djcelery.setup_loader()
BROKER_URL = 'redis://127.0.0.1:6379/1'
CELERY_IMPORTS = ('art.tasks')
CELERY_TIMEZONE = 'Asia/Shanghai'
CELERYBEAT_SCHEDULER = 'djcelery.schedulers.DatabaseScheduler' 
from celery.schedules import crontab
from celery.schedules import timedelta

CELERYBEAT_SCHEDULE = {    #定時器策略
   #定時任務一: 每隔30s執行一次
   u'測試定時器1': {
       "task": "art.tasks.tsend_email",
       #"schedule": crontab(minute='*/2'),  # or 'schedule':   timedelta(seconds=3),
       "schedule":timedelta(seconds=30),
       "args": (),
   },
}
#############################
# celery 配置資訊 end
#############################

當 djcelery.setup_loader() 執行時,Celery 便會去檢視 INSTALLD_APPS 下包含的所有 app 目錄中的 tasks.py 檔案,找到標記為task 的方法,將它們註冊為 celery task.

BROKER_URL:broker 是代理人,它負責分發任務給 worker 去執行。我使用的是 Redis 作為 broker。沒有設定 CELERY_RESULT_BACKEND,預設沒有配置,此時 Django 會使用預設的資料庫(也是你指定的 orm 資料庫)。

CELERY_IMPORTS:是匯入目標任務檔案。

CELERYBEAT_SCHEDULER:使用了 django-celery 預設的資料庫排程模型,任務執行週期都被存在預設指定的 orm 資料庫中。

CELERYBEAT_SCHEDULE:設定定時的時間配置, 可以精確到秒,分鐘,小時,天,周等。

(3)建立應用例項

在主工程目錄新增 celery.py, 新增自動檢索 django 工程 tasks 任務

vim artproject/celery.py

#!/usr/bin/env python  
# encoding: utf-8  
#目的是拒絕隱式引入,celery.py和celery衝突。
from __future__ import absolute_import,unicode_literals 
import os
from celery import Celery
from django.conf import settings

# 設定環境變數
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "artproject.settings")

#建立celery應用
app = Celery('art_project')
app.config_from_object('django.conf:settings')

#如果在工程的應用中建立了tasks.py模組,那麼Celery應用就會自動去檢索建立的任務。比如你新增了一個任#務,在django中會實時地檢索出來。
app.autodiscover_tasks(lambda :settings.INSTALLED_APPS)

(4)建立任務 tasks

每個任務本質上就是一個函式,在 tasks.py 中,寫入你想要執行的函式即可。

在應用 art 中新增我們需要提供的非同步服務和定時服務。

vim art/tasks.py

#!/usr/bin/env python  
# encoding: utf-8  
from __future__ import absolute_import
import time
from django.core.mail import send_mail
from celery.utils.log import get_task_logger
from artproject.celery import app

from art.utils.send_mail import pack_html, send_email

@app.task
def tsend_email():
  url = "http://1000phone.com"
  receiver = 'diyuhuan@1000phone.com'
  content = pack_html(receiver, url)
  # content = 'this is email content.'
  send_email(receiver, content)
  print('send email ok!')


@app.task
def add(x, y):
  return x+y

上述我們把非同步處理任務 add 和定時器任務 tsend_email 都放在了 tasks.py 中

(5)遷移生成 celery 需要的資料表

python manage.py migrate

此時資料庫表結構多出了幾個

celery_taskmeta            |
| celery_tasksetmeta         |
| djcelery_crontabschedule   |
| djcelery_intervalschedule  |
| djcelery_periodictask      |
| djcelery_periodictasks     |
| djcelery_taskstate         |
| djcelery_workerstate

3、啟動服務,測試

我們可以採用 python manage.py help 發現多出了 celery 相關選項。

(1)啟動 django celery 服務

啟動服務:

python manage.py celery worker --loglevel=info

此時非同步處理和定時處理服務都已經啟動了

(2)web 端介面觸發非同步任務處理

我們在 web 端加入一個入口,觸發非同步任務處理 add 函式

在應用 art 的 urls.py 中加入如下對應關係

from art.views import add_handler


url(r'^add', add_handler),

art/views.py 中加入處理邏輯

def add_handler(request):
  x = request.GET.get('x', '1')
  y = request.GET.get('y', '1')
  from .tasks import add
  add.delay(int(x), int(y))
  res = {'code':200, 'message':'ok', 'data':[{'x':x, 'y':y}]}
  return HttpResponse(json.dumps(res))

啟動 web 服務,通過 url 傳入的引數,通過 handler 的 add.delay(x, y) 計算並存入 mysql

http://127.0.0.1:8000/art/add?x=188&y=22

(3)測試定時器,傳送郵件

在終端輸入 python manage.py celerybeat -l info

會自動觸發每隔 30s 執行一次 tsend_email 定時器函式,傳送郵件:

CELERYBEAT_SCHEDULE = {    #定時器策略
   #定時任務一: 每隔30s執行一次
   u'測試定時器1': {
       "task": "art.tasks.tsend_email",
       #"schedule": crontab(minute='*/2'),  # or 'schedule': timedelta(seconds=3),
       "schedule":timedelta(seconds=30),
       "args": (),
   },
}

具體傳送郵件服務程式見下面的第 4 節

4、郵件傳送服務

專案中經常會有定時傳送郵件的情形,比如傳送資料包告,傳送異常服務報告等。

可以編輯檔案 art/utils/send_mail.py, 內容編輯如下:

#!/usr/bin/env python
#-*- coding:utf-8 -*-
#written by diyuhuan
#傳送郵件(wd_email_check123賬號用於內部測試使用,不要用於其他用途)

import smtplib  
from email.mime.multipart import MIMEMultipart  
from email.mime.text import MIMEText  
from email.mime.image import MIMEImage 
from email.header import Header
import time

sender = 'wd_email_check123@163.com'  
subject = u'api開放平臺郵箱驗證'
smtpserver = 'smtp.163.com'
username = 'wd_email_check123'
password = 'wandacheck1234'
mail_postfix="163.com"

def send_email(receiver, content):
   try:
       me = username+"<"+username+"@"+mail_postfix+">"
       msg = MIMEText(content, 'html', 'utf-8')
       msg['Subject'] = subject
       msg['From'] = sender
       msg['To'] = receiver
       smtp = smtplib.SMTP()  
       smtp.connect(smtpserver)  
       smtp.login(username, password)
       smtp.sendmail(sender, receiver, msg.as_string())  
       smtp.quit()
       return True
   except Exception as e:
       print('send_email has error with : ' + str(e))
       return False


def pack_html(receiver, url):
   html_content = u"<html><div>尊敬的使用者<font color='#0066FF'>%s</font> 您好!</div><br>" \
                  "<div>感謝您關注我們的平臺 ,我們將為您提供最貼心的服務,祝您購物愉快。</div><br>" \
                  "<div>點選以下連結,即可完成郵箱安全驗證:</div><br>"  \
                  "<div><a href='%s'>%s</a></div><br>"  \
                  "<div>為保障您的帳號安全,請在24小時內點選該連結; </div><br>" \
                  "<div>若您沒有申請過驗證郵箱 ,請您忽略此郵件,由此給您帶來的不便請諒解。</div>" \
                  "</html>" % (receiver, url, url)
   html_content = html_content
   return html_content


if __name__ == "__main__":
   url = "http://1000phone.com"
   receiver = 'diyuhuan@1000phone.com'
   #content = pack_html(receiver, url)
   content = 'this is email content. at %s.'%int(time.time())
   send_email(receiver,  content)

至此,在 celery ui 介面可以看到兩類,定時器處理和非同步處理。

5、啟動 flower 服務

python manager celery flower

案例

7209287-4a3d3bc7f170f2d4.png

讀書網站實現搶讀功能

qd(request, id) :搶讀檢視函式

quereyQD(request,id) :查詢搶讀的檢視函式

settings.py

INSTALLED_APPS = [
  'djcelery',
]

...
import djcelery

# 裝載djcelery物件
djcelery.setup_loader()
# 配置訊息中介軟體的位置
BROKER_URL = 'redis://127.0.0.1:6379/12'

CELERY_TIMEZONE = 'Asia/Shanghai'
# 配置批量偵錯程式
CELERYBEAT_SCHEDULER = 'djcelery.schedulers.DatabaseScheduler'

在主工程目錄新增 celery.py, 新增自動檢索 django 工程 tasks任務

celery.py

from __future__ import absolute_import
import os
from celery import Celery

# 設定環境變數
from HArtPro import settings

os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'HArtPro.settings')

# 建立Celery物件
app = Celery('hart')

# 載入配置
app.config_from_object('django.conf:settings')

# 自動發現task的非同步任務
app.autodiscover_tasks(lambda :settings.INSTALLED_APPS)

當前 app 目錄下建立 tasks.py

from MArtPro.celery import app
from utils import redis_cache


@app.task
def advanceArt(artId, userId):
   # 搶讀文章(artId 文章id, userId 當前使用者登入的Id)
   print('使用者', userId, '正在搶讀', artId)

   # 判斷當前搶讀的hash物件AdvanceArt長度是否達到5個
   if redis_cache.hlen('AdvanceArt') >= 5:
       return artId + '搶讀失敗'

   redis_cache.hset('AdvanceArt', userId, artId)

   return artId + '搶讀成功!'
views.py
from redis_ import rd  # rd 物件
from art import tasks

def (request, artId):
   # 搶讀
   login_user = request.session.get('login_user')

   if not login_user:
       return JsonResponse({'status': 101,
                            'msg': '親,請先登入,再搶讀,謝謝!'})

   # 判斷當前使用者是否已搶過
   user_id =json.loads(login_user).get('id')
   if redis_cache.hexists('AdvanceArt', user_id):
       return JsonResponse({'status': 205,
                            'msg': '親,你只能搶一本'})
   # 任務延遲執行
   tasks.advanceArt.delay(artId, user_id)
   return JsonResponse({'status': 201,
                        'msg': '正在搶讀...'})


def queryAdvance(request, artId):
   # 查詢搶讀是否成功
   login_user = request.session.get('login_user')

   if not login_user:
       return JsonResponse({'status': 101,
                            'msg': '親,請先登入,再檢視搶讀,謝謝!'})

   user_id = json.loads(login_user).get('id')

   artId = redis_cache.hget('AdvanceArt', user_id)
   if artId:
       art = Art.objects.get(id=artId.decode())
       return JsonResponse({'status': 200,
                            'msg': '恭喜您,搶讀%s 成功'%art.title})
   else:
       if redis_cache.hlen('AdvanceArt')< 5:
           return JsonResponse({'status': 202,
                                'msg': '正在搶讀...'})
       else:
           return JsonResponse({'status': 203,
                                'msg': '搶讀失敗, 請下次碰碰運氣!'})

前端通過定時器,每秒執行查詢函式

作者:rottengeek
轉載|原文連結:https://segmentfault.com/u/rottengeek
(如有侵權,請聯絡刪除)

最新公告通知

第 19 期【Python自動化運維入門】正在火熱招生中
第 8 期 【Python自動化運維進階】正在火熱招生中

7209287-1e2acc9a64fb987f.png

相關文章