[Django] Learn Django Celery with RabbitMQ

热身练习


pip install celery
sudo apt-get install rabbitmq-server
systemctl status rabbitmq
-server

 

1. 配置好

复制代码
 from __future__ import absolute_import, unicode_literals
 import os
 from celery import Celery
 
# 绑定 os.environ.setdefault(
'DJANGO_SETTINGS_MODULE', 'proj.settings') app = Celery('proj') app.config_from_object('django.conf:settings', namespace='CELERY')
# 启动 app.autodiscover_tasks()
复制代码

 

2. 启动服务

(venv) jeffrey@jeffrey-EnlargeDS:~/.../YT-Django-Celery-Series-Intro-Install-Run-Task$ sudo systemctl start rabbitmq-server
[sudo] password for jeffrey: 
(venv) jeffrey@jeffrey-EnlargeDS:~/.../YT-Django-Celery-Series-Intro-Install-Run-Task$ celery -A proj worker -l info
复制代码
(venv) jeffrey@jeffrey-EnlargeDS:~/.../YT-Django-Celery-Series-Intro-Install-Run-Task$ sudo systemctl start rabbitmq-server
[sudo] password for jeffrey: 
(venv) jeffrey@jeffrey-EnlargeDS:~/.../YT-Django-Celery-Series-Intro-Install-Run-Task$ 
(venv) jeffrey@jeffrey-EnlargeDS:~/.../YT-Django-Celery-Series-Intro-Install-Run-Task$ 
(venv) jeffrey@jeffrey-EnlargeDS:~/.../YT-Django-Celery-Series-Intro-Install-Run-Task$ celery -A proj worker -l info
[2023-05-12 01:41:29,670: WARNING/MainProcess] No hostname was supplied. Reverting to default 'localhost'
 
 -------------- celery@jeffrey-EnlargeDS v5.2.7 (dawn-chorus)
--- ***** ----- 
-- ******* ---- Linux-5.19.0-41-generic-x86_64-with-glibc2.35 2023-05-12 01:41:29
- *** --- * --- 
- ** ---------- [config]
- ** ---------- .> app:         proj:0x7f02a03ec8b0
- ** ---------- .> transport:   amqp://guest:**@localhost:5672//
- ** ---------- .> results:     disabled://
- *** --- * --- .> concurrency: 8 (prefork)
-- ******* ---- .> task events: OFF (enable -E to monitor tasks in this worker)
--- ***** ----- 
 -------------- [queues]
                .> celery           exchange=celery(direct) key=celery
                

[tasks]
  . app1.tasks.add

[2023-05-12 01:41:29,914: INFO/MainProcess] Connected to amqp://guest:**@127.0.0.1:5672//
[2023-05-12 01:41:29,917: INFO/MainProcess] mingle: searching for neighbors
[2023-05-12 01:41:29,922: WARNING/MainProcess] No hostname was supplied. Reverting to default 'localhost'
[2023-05-12 01:41:30,999: INFO/MainProcess] mingle: all alone
[2023-05-12 01:41:31,024: WARNING/MainProcess] /home/jeffrey/Desktop/web/YT-Django-Celery-Series-Intro-Install-Run-Task/venv/lib/python3.10/site-packages/celery/fixups/django.py:203: UserWarning: Using settings.DEBUG leads to a memory
            leak, never use this setting in production environments!
  warnings.warn('''Using settings.DEBUG leads to a memory

[2023-05-12 01:41:31,024: INFO/MainProcess] celery@jeffrey-EnlargeDS ready.
Log 服务都在开启状态
复制代码

 

3. 然后,创建具体的任务

 from __future__ import absolute_import, unicode_literals
 from celery import shared_task
 
 @shared_task
 def add(x, y):
     return x + y

测试:

>>> from app1.tasks import add

>>> add.delay(4,4) <AsyncResult: fa9c5e8b-73e5-488e-862d-f1068b07423b> >>> add.delay(4,4) <AsyncResult: e4d8380f-2f01-4553-932e-120d8dea7ca6>

看到结果:

复制代码
[2023-05-12 01:41:31,024: INFO/MainProcess] celery@jeffrey-EnlargeDS ready.


[2023-05-12 01:43:07,056: INFO/MainProcess] Task app1.tasks.add[fa9c5e8b-73e5-488e-862d-f1068b07423b] received
[2023-05-12 01:43:07,057: INFO/ForkPoolWorker-8] Task app1.tasks.add[fa9c5e8b-73e5-488e-862d-f1068b07423b] succeeded in 0.00028356400071061216s: 8


[2023-05-12 01:43:20,371: INFO/MainProcess] Task app1.tasks.add[e4d8380f-2f01-4553-932e-120d8dea7ca6] received
[2023-05-12 01:43:20,372: INFO/ForkPoolWorker-8] Task app1.tasks.add[e4d8380f-2f01-4553-932e-120d8dea7ca6] succeeded in 0.00012868200064986013s: 8
View Code
复制代码

异步创建多个任务:

>>> add.apply_async((3,3), countdown=10)
<AsyncResult: a9a12796-0972-4f99-af72-8c20031e0138>
>>> add.apply_async((3,3), countdown=1)
<AsyncResult: 89676a07-1b98-42ba-8b15-ed6edea33308>

 

 

 

简单案例


 

 

邮箱配置

  • Setting.py

Manage your Google Account.

创建如下,得到一个”密码“。

配置如下:

复制代码
 # gmail_send/settings.py
 EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
 EMAIL_HOST = 'smtp.gmail.com'
 EMAIL_HOST_USER = 'veryacademydemo@gmail.com'
 EMAIL_HOST_PASSWORD = 'jaazriuaslxfpwpg'
 EMAIL_PORT = 587
 EMAIL_USE_TLS = True
 DEFAULT_FROM_EMAIL = 'veryacademydemo@gmail.com'
View Code
复制代码
  • email.py

Email有不同的服务商。

复制代码
 from django.template import Context
 from django.template.loader import render_to_string
 from django.core.mail importEmailMessage
from django.conf import settings
 
 def send_review_email(name, email, review):
 
     context = {
         'name': name,
         'email': email,
         'review': review,
     }
 
     email_subject = 'Thank you for your review'
     email_body    = render_to_string('email_message.txt', context)
 
# 定义具体的email的服务商 email
= EmailMessage( email_subject, email_body, settings.DEFAULT_FROM_EMAIL, [email, ], ) return email.send(fail_silently=False)
复制代码

 

异步任务 Celery

  • task.py

定义asyn task。

复制代码
 from celery.decorators import task
 from celery.utils.log import get_task_logger
from .email import send_review_email
 
 logger = get_task_logger(__name__)
  
 @task(name="send_review_email_task")  # 这里没有用 shared_task
 def send_review_email_task(name, email, review):
     logger.info("Sent review email")
     return send_review_email(name, email, review)
复制代码

 

提交表格

  • forms.py

这里定义了form的变量。

复制代码
 from django import forms
 from task2.tasks import send_review_email_task
 
 class ReviewForm(forms.Form):
name
= forms.CharField( label='Firstname', min_length=4, max_length=50, widget=forms.TextInput( attrs={'class': 'form-control mb-3', 'placeholder': 'Firstname', 'id': 'form-firstname'})) email = forms.EmailField( max_length=200, widget=forms.TextInput( attrs={'class': 'form-control mb-3', 'placeholder': 'E-mail', 'id': 'form-email'})) review = forms.CharField( label="Review", widget=forms.Textarea(attrs={'class': 'form-control', 'rows': '5'})) def send_email(self): send_review_email_task.delay( self.cleaned_data['name'], self.cleaned_data['email'], self.cleaned_data['review'])
复制代码
  • templates/review.html

form对应的html ui如下。

复制代码
       <div class="container-fluid">
           <div class="row no-gutter">
               <div class="col-md-6 bg-light">
                   <div class="login d-flex align-items-center py-5">
                       <div class="container">
                           <div class="row">
                               <div class="col-8 col-md-8  mx-auto">
                                   <p class="h4 mb-4 font-weight-bold">Write a review</p>
                                   <form action="{% url 'reviews' %}" method="post">{% csrf_token %}
                                       {{ form.as_p }}
                                       <button class="btn btn-dark btn-block py-2 mb-4 mt-5 font-weight-bold" type="submit" value="Log-in">Submit</button>
                                   </form>
                               </div>
                           </div>
                       </div>
                   </div>
               </div>
               <div class="col-md-6 d-none d-md-flex bg-image"></div>
           </div>
       </div>
复制代码

 

Request接口

  • views.py

界面,但form的定义可以单独另外定义;这里只“invoke method":send_email()。

复制代码
 from task2.forms import ReviewForm
 from django.views.generic.edit import FormView
 from django.http import HttpResponse
  
 class ReviewEmailView(FormView):
     template_name = 'review.html'form_class= ReviewForm
 
     def form_valid(self, form):
         form.send_email()
         msg = "Thanks for the review!"
         return HttpResponse(msg)
复制代码
  •  urls.py
from django.contrib import admin
 from django.urls import path
 from task2.views import ReviewEmailView
 
 urlpatterns = [
     path('admin/', admin.site.urls),
     path('reviews/', ReviewEmailView.as_view(), name="reviews"),
 ]

 

 

Django + Celery 学习笔记

  • 原理解析

Ref: Django+Celery学习笔记1——任务队列介绍

任务队列一般用于线程或计算机之间分配工作的一种机制。

任务队列的输入是一个称为任务的工作单元,有专门的职程(Worker)进行不断的监视任务队列,进行执行新的任务工作。

Celery 通过消息机制进行通信,通常使用中间人(Broker)作为 客户端职程(Worker)调节。启动一个任务,客户端向消息队列发送一条消息,然后中间人(Broker)将消息传递给一个职程(Worker),最后由职程(Worker)进行执行中间人(Broker)分配的任务。

Client 发送消息给 ”消息队列“ (代理是 RabbitMQ 或 Redis) --> Broker --> 分配 msg 给一个 Worker,并执行。
Celery 可以有多个职程(Worker)和中间人(Broker),用来提高Celery的高可用性以及横向扩展能力。

 

 

  • Celery特性描述

1、方便查看定时任务的执行情况, 如 是否成功, 当前状态, 执行任务花费的时间等.

2、使用功能齐备的管理后台或命令行添加,更新,删除任务.

3、方便把任务和配置管理相关联.

4、可选 a) 多进程, b) Eventlet 和 c) Gevent 三种模型并发执行.

5、提供错误处理机制.

6、提供多种任务原语, 方便实现任务分组,拆分,和调用链.

7、支持多种消息代理和存储后端.

8、Celery 是语言无关的.它提供了python 等常见语言的接口支持.

 

Ref: Python的eventlet使用与理解

Eventlet

​Coroutine,翻译成”协程“,初始碰到的人马上就会跟上面两个概念联系起来。直接先说区别,Coroutine是编译器级的,Process和Thread是操作系统级的。Coroutine的实现,通常是对某个语言做相应的提议,然后通过后成编译器标准,然后编译器厂商来实现该机制。Process和Thread看起来也在语言层次,但是内生原理却是操作系统先有这个东西,然后通过一定的API暴露给用户使用,两者在这里有不同。Process和Thread是os通过调度算法,保存当前的上下文,然后从上次暂停的地方再次开始计算,重新开始的地方不可预期,每次CPU计算的指令数量和代码跑过的CPU时间是相关的,跑到os分配的cpu时间到达后就会被os强制挂起。Coroutine是编译器的魔术,通过插入相关的代码使得代码段能够实现分段式的执行,重新开始的地方是yield关键字指定的,一次一定会跑到一个yield对应的地方。

总之,对于Coroutine,是编译器帮助做了很多的事情,来让代码不是一次性的跑到底,而不是操作系统强制的挂起。代码每次跑多少,是可预期的。但是,Process和Thread,在这个层面上完全不同,这两个东西是操作系统管理的。

celery==4.3.0
Django==2.2.2
django-celery-beat==1.5.0
django-celery-results==1.1.2
kombu==4.6.11   -- celery的依赖
PyMySQL==0.9.3
redis==3.2.1
python-crontab==2.5.1   -- 用于设置周期性被执行的指令

 

在 django的项目目录(djangocelerydemo)中 创建celery.py (与settings.py在同一级目录) 文件,当然你也可以命名成 celeryconfig.py 文件,

这个文件没有要求,为啥要创建这个文件呢? 

因为,要将Celery与Django项目一起使用,必须首先定义Celery库的实例,也就是创建celery的应用。

文件放在此处,这种设置方法可以让celery自动在所有app中查找tasks文件,比较适合多人多APP同时开发的中大型项目 详情参考:Using Celery with Django

 

复制代码
CELERY_ENABLE_UTC = False
# 不使用国际标准时间
CELERY_TIMEZONE = 'Asia/Shanghai'
# 使用亚洲/上海时区
DJANGO_CELERY_BEAT_TZ_AWARE = False
# 解决时区问题
CELERY_BROKER_URL = 'redis://127.0.0.1:6379/0'
# 使用0号数据库

CELERY_BROKER_TRANSPORT = 'redis'
# 使用redis作为中间件
CELERY_BEAT_SCHEDULER = 'django_celery_beat.schedulers:DatabaseScheduler' # 自定义调度类,使用Django的ORM
CELERY_RESULT_BACKEND = 'django-db' # 任务结果,使用Django的ORM CELERY_ACCEPT_CONTENT = ['application/json'] # 设置任务接收的序列化类型 CELERY_TASK_SERIALIZER = 'json' # 设置任务序列化方式 CELERY_RESULT_SERIALIZER = 'json' # 设置结果序列化方式
复制代码
复制代码
 CELERY_BEAT_SCHEDULE = {
     "scheduled_task": {
         "task": "task1.tasks.add",
         "schedule": 5.0,
         "args": (10, 10),
     },
     "database": {
         "task": "task3.tasks.bkup",
         "schedule": 5.0,
     },
 }
复制代码

 

复制代码
from __future__ import absolute_import, unicode_literals
import os
from celery import Celery, platforms
from django.utils.datetime_safe import datetime
 
# 获取当前文件夹名,即为该 Django 的项目名
project_name = os.path.split(os.path.abspath('.'))[-1]
project_settings = '%s.settings' % project_name
print(project_settings)
 
# 设置默认celery命令行的环境变量
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'djangocelerydemo.settings')  # ***
  
# 实例化 Celery,项目名称
app = Celery('djangocelerydemo')  # ***
 
# 解决时区问题
app.now = datetime.now
 
# 使用 django 的 settings 文件配置 celery
app.config_from_object('django.conf:settings', namespace='CELERY')  # ***
 
# 从所有应用中加载任务模块tasks.py
app.autodiscover_tasks()  # ***
 
# 解决celery不能root用户启动的问题
platforms.C_FORCE_ROOT = True
复制代码
复制代码
from __future__ import absolute_import, unicode_literals
import os
from celery import Celery
 
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'core.settings')  # ***
app = Celery('core')  # ***
app.config_from_object('django.conf:settings', namespace='CELERY')  # ***
app.autodiscover_tasks()  # ***
 
@app.task(bind=True)
def debug_task(self):
    print('Request: {0!r}'.format(self.request))
复制代码

 

复制代码
# 引入celery实例对象
from __future__ import absolute_import, unicode_literals
from djangocelerydemo.celeryconfig import app as celery_app
 
__all__ = ('celery_app',)
 
 
import pymysql
pymysql.install_as_MySQLdb()
复制代码

 

复制代码
# Create your tasks here
from __future__ import absolute_import, unicode_literals
from djangocelerydemo.celeryconfig import app
 
@app.task
def plan_task_1():
    print("任务_1已运行!")
    return {"任务_1:success"}
  
@app.task
def plan_task_2():
    print("任务_2已运行!")
    return  {"任务_2:success"}
复制代码
复制代码
from __future__ import absolute_import, unicode_literals
import sys
from celery import shared_task
from django.core.management import call_command
import sys
  
@shared_task
def bkup():
    sys.stdout = open('db.json', 'w')
    call_command('dumpdata', 'task3')
复制代码

 

from django.apps import AppConfig
 
class CelerytestConfig(AppConfig):
    name = 'celerytest'

 

 

 

posted @   郝壹贰叁  阅读(97)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律
点击右上角即可分享
微信分享提示