celery mysql flask_用 Flask 来写个轻博客 (26) — 使用 Flask-Celery-Helper 实现异步任务...

本文介绍了如何使用 Celery 和 Flask-Celery-Helper 在 Flask 应用中实现异步任务,重点讲述了配置 Celery 与 RabbitMQ 的连接,创建 Celery 对象并注册到 Flask 应用,以及通过 SQLAlchemy 事件触发 Celery 任务发送欢迎邮件。
摘要由CSDN通过智能技术生成

目录

前文列表

扩展阅读

Celery

Celery 是使用 Python 多任务库来编写的任务队列工具, 可以 并行 的执行任务. 我们会将执行时间较长但又不那么追求实时的功能以异步任务的形式完成, EG. 上传文件, 发送邮件…, Python 和 Celery 之间需要一个中间人(消息队列)来进行任务队列的管理, Celery 官方推荐使用 RabbirMQ 或 Redis 来充当这个角色. 当然也可以同时使用两者, 其中 MQ 作为中间人, Redis 传递 Celery 执行的结果给应用端. 这样做的优势在于, 返回给应用的结果是持久化保存在数据库中的.

消息队列: 是一种专门设计的系统, 用于在生产者(往队列发送消息的程序)和消费者(从队列中取出消息的队列)之间传递消息.

e818905193d0007cb28b12f27f5d9f11.png

安装 Celery

pip install Celery

安装 Flask-Celery-Helper

Flask-Celery-Helper 是一个 Flask 扩展, 用于辅助使用 app 来初始化 Celery 对象, 使其得以注册到 app 对象中.

pip install Flask-Celery-Helper

pip freeze > requirments.txt

安装 RabbitMQ: 官方安装文档

启动 RabbitMQ

/etc/init.d/rabbitmq-server start

将 Celery 加入到应用中

配置 Celery 连接 RabbitMQ 的 URL

vim jmilkfansblog/config.py

class DevConfig(Config):

"""Development config class."""

...

# Celery RabbitMQ connection

CELERY_RESULT_BACKEND = "amqp://guest:guest@localhost:5672//"

CELERY_BROKER_URL = "amqp://guest:guest@localhost:5672//"

NOTE: RabbitMQ 使用默认的 guest 用户, 端口为 5672

创建 celery 对象

vim jmilkfansblog/extensions.py

from flask.ext.celery import Celery

...

# Create the Flask-Celery-Helper's instance

flask_celery = Celery()

将 celery 对象注册到 app 对象

vim jmilkfansblog/__init__.py

from jmilkfansblog.extensions import flask_celery

def create_app(object_name):

"""Create the app instance via `Factory Method`"""

...

# Init the Flask-Celery-Helper via app object

# Register the celery object into app object

flask_celery.init_app(app)

NOTE 1: Celery 的进程必须在 Flask app 的上下文中运行, 这样 Celery 才能够跟其他的 Flask 扩展协同工作. 所以必须将 flask_celery 对象注册到 app 对象中, 并且每创建一个 Celery 进程都需要创建一个新的 Flask app 对象, 这里我们使用工厂模式来创建 celery 对象.

NOTE 2: flask_celery 对象是 Flask-Celery-Helper 扩展的对象, 用于辅助处理 Celery 的初始化, 所以实际上我们是可以不使用这个扩展, 而直接使用 Celery 的. celery 对象才是真正的 Celery 的对象.

使用工厂模式来创建 celery 对象

./celery_runner.py

mport os

from celery import Celery

from jmilkfansblog import create_app

def make_celery(app):

"""Create the celery process."""

# Init the celery object via app's configuration.

celery = Celery(

app.import_name,

backend=app.config['CELERY_RESULT_BACKEND'],

broker=app.config['CELERY_BROKER_URL'])

# Flask-Celery-Helpwe to auto-setup the config.

celery.conf.update(app.config)

TaskBase = celery.Task

class ContextTask(TaskBase):

abstract = True

def __call__(self, *args, **kwargs):

"""Will be execute when create the instance object of ContextTesk."""

# Will context(Flask's Extends) of app object(Producer Sit)

# be included in celery object(Consumer Site).

with app.app_context():

return TaskBase.__call__(self, *args, **kwargs)

# Include the app_context into celery.Task.

# Let other Flask extensions can be normal calls.

celery.Task = ContextTask

return celery

env = os.environ.get('BLOG_ENV', 'dev')

flask_app = create_app('jmilkfansblog.config.%sConfig' % env.capitalize())

# 1. Each celery process needs to create an instance of the Flask application.

# 2. Register the celery object into the app object.

celery = make_celery(flask_app)

NOTE 1: 我们以后会以 CLI 的形式来管理和控制 Celery 的 worker, 所以我们将 celery 对象的实现模块放置在 ./celery_runner.py 中, 而不是 jmilkfansblog/celery_runner.py. Flask app 内部的 tasks 任何的定义和实现就交由 Flask-Celery-Helper 来支持就好了, 这也是 Flask-Celery-Helper 存在的意义.

NOTE 2: make_celery()最重要的作用就是让每个 Celery 的进程中(celery对象)都包含有 app 对象的上下文, 至于为什么这么做呢? 上述已经给出了答案.

NOTE 3: 这里通过 create_app() 创建的对象不能够命名为 app, 而是命名为 flask_app, 这是因为 Celery 会默认将命名为 app 或 celery 的对象都作为一个 Celery 对象来处理.

NOTE 4: celery.conf.update(app.config) 会将 app 对象的 config 更新到 celery 对象中, 当然也包括了刚刚定义的 RabbitMQ 连接 URL 配置.

启动 Celery 服务

(env) jmilkfan@JmilkFan-Devstack:/opt/JmilkFan-s-Blog$ celery worker -A celery_runner --loglevel=info

...

-------------- celery@JmilkFan-Devstack v4.0.1 (latentcall)---- **** -------- * *** * -- Linux-4.4.0-53-generic-x86_64-with-Ubuntu-16.04-xenial 2016-12-15 19:12:33-- * - **** ----** ---------- [config]-** ---------- .> app: jmilkfansblog:0x7f5b24345990-** ---------- .> transport: amqp://guest:**@localhost:5672//-** ---------- .> results: amqp://-*** --- * --- .> concurrency: 4 (prefork)-- ******* ---- .> task events: OFF (enable -E to monitor tasks in this worker)--- ***** ----- -------------- [queues] .> celery exchange=celery(direct) key=celery

[tasks]

. jmilkfansblog.tasks.digest

. jmilkfansblog.tasks.log

. jmilkfansblog.tasks.multiply

. jmilkfansblog.tasks.remind

NOTE: 在启动 Celery 服务的时候, 能够用 Output 看见其自身的配置信息和现在所处理的 tasks.

实现向新用户发送欢迎邮件

下面使用 Celery 实现在用户创建账户之后, 在指定的时间内异步的向新用户发送欢迎邮件.

添加 Reminder Model 用户存放用户的 email 地址和欢迎内容.

vim jmilkfansblog/models.py

class Reminder(db.Model):

"""Represents Proected reminders."""

__tablename__ = 'reminders'

id = db.Column(db.String(45), primary_key=True)

date = db.Column(db.DateTime())

email = db.Column(db.String(255))

text = db.Column(db.Text())

def __init__(self, id, text):

self.id = id

self.email = text

def __repr__(self):

return ''.format(self.text[:20])

创建一个 task

vim jmilkfansblog/tasks.py

import smtplib

import datetime

from email.mime.text import MIMEText

from flask_mail import Message

from jmilkfansblog.extensions import flask_celery, mail

@flask_celery.task(

bind=True,

igonre_result=True,

default_retry_delay=300,

max_retries=5)

def remind(self, primary_key):

"""Send the remind email to user when registered.

Using Flask-Mail.

"""

reminder = Reminder.query.get(primary_key)

msg = MIMEText(reminder.text)

msg['Subject'] = 'Welcome!'

msg['FROM'] =

msg['To'] = reminder.email

try:

smtp_server = smtplib.SMTP('localhost')

smtp_server.starttls()

smtp_server.login(, )

smtp_server.sendmail(,

[reminder.email],

msg.as_string())

smtp_server.close()

return

except Exception as err:

self.retry(exc=err)

def on_reminder_save(mapper, connect, self):

"""Callbask for task remind."""

remind.apply_async(args=(self.id), eta=self.date)

NOTE 1: Celery Task 本质上就是一个被 celery.task()装饰过的函数,

NOTE 2: 使用主键 primary_key 来获取 reminder 对象是为了避免数据竞态的发生, 因为从生成 reminder 对象到 task 被执行的过程并不能保证数据是最新的, 这也是处理异步调用时, 需要时刻注意的地方.

NOTE 3: on_reminder_save() 是一个回调函数, 当我们在一个特定的情景下调用这个函数的时候就触发了一个 Celery task.

应用 SQLAlchemy 的 event 特性来触发 Celery task

vim jmilkfansblog/__init__.py

from sqlalchemy import event

def create_app(object_name):

"""Create the app instance via `Factory Method`"""

...

# Will be callback on_reminder_save when insert recond into table `reminder`.

event.listen(Reminder, 'after_insert', on_reminder_save)

NOTE 1: SQLAlchemy 允许在 Model 上注册回调函数, 当 Model 对象发生特定的情景时, 就会执行这个回调函数, 这就是所谓的 event, 这里我们使用 after_insert 来指定当创建一个新的 Reminder 对象(插入一条记录)时就触发这个回调函数. 而是回调函数中的形参, 会由 event 来负责传入.

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值