Blog 项目源码:https://github.com/JmilkFan/JmilkFan-s-Blog

目录

  • 目录
  • 前文列表
  • 扩展阅读
  • Celery
  • 将 Celery 加入到应用中
  • 实现向新用户发送欢迎邮件

前文列表

用 Flask 来写个轻博客 (1) — 创建项目
用 Flask 来写个轻博客 (2) — Hello World!
用 Flask 来写个轻博客 (3) — (M)VC_连接 MySQL 和 SQLAlchemy
用 Flask 来写个轻博客 (4) — (M)VC_创建数据模型和表
用 Flask 来写个轻博客 (5) — (M)VC_SQLAlchemy 的 CRUD 详解
用 Flask 来写个轻博客 (6) — (M)VC_models 的关系(one to many)
用 Flask 来写个轻博客 (7) — (M)VC_models 的关系(many to many)
用 Flask 来写个轻博客 (8) — (M)VC_Alembic 管理数据库结构的升级和降级
用 Flask 来写个轻博客 (9) — M(V)C_Jinja 语法基础快速概览
用 Flask 来写个轻博客 (10) — M(V)C_Jinja 常用过滤器与 Flask 特殊变量及方法
用 Flask 来写个轻博客 (11) — M(V)C_创建视图函数
用 Flask 来写个轻博客 (12) — M(V)C_编写和继承 Jinja 模板
用 Flask 来写个轻博客 (13) — M(V)C_WTForms 服务端表单检验
用 Flask 来写个轻博客 (14) — M(V)C_实现项目首页的模板
用 Flask 来写个轻博客 (15) — M(V)C_实现博文页面评论表单
用 Flask 来写个轻博客 (16) — MV(C)_Flask Blueprint 蓝图
用 Flask 来写个轻博客 (17) — MV(C)_应用蓝图来重构项目
用 Flask 来写个轻博客 (18) — 使用工厂模式来生成应用对象
用 Flask 来写个轻博客 (19) — 以 Bcrypt 密文存储账户信息与实现用户登陆表单
用 Flask 来写个轻博客 (20) — 实现注册表单与应用 reCAPTCHA 来实现验证码
用 Flask 来写个轻博客 (21) — 结合 reCAPTCHA 验证码实现用户注册与登录
用 Flask 来写个轻博客 (22) — 实现博客文章的添加和编辑页面
用 Flask 来写个轻博客 (23) — 应用 OAuth 来实现 Facebook 第三方登录
用 Flask 来写个轻博客 (24) — 使用 Flask-Login 来保护应用安全
用 Flask 来写个轻博客 (25) — 使用 Flask-Principal 实现角色权限功能

扩展阅读

Celery-分布式任务队列
基于后台作业的 Celery
Flask-Celery-Helper 1.1.0

Celery

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

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

  • 安装 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 connectionCELERY_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_celerydef 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 objectflask_celery.init_app(app)

NOTE 1: Celery 的进程必须在 Flask app 的上下文中运行, 这样 Celery 才能够跟其他的 Flask 扩展协同工作。所以必须将 flask_celery 对象注册到 app 对象中, 并且每创建一个 Celery 进程都需要创建一个新的 Flask app 对象, 这里我们使用工厂模式来创建 celery 对象。这一点是非常重要的,实际上 Flask application 和 Celery application 是两个不同的进程,在 Celery 没有加入 Flask 上下文的情况下,Celery 的程序逻辑就不能轻易的访问 Flask 相关资源,比如不能加载 Flask 的环境配置信息,无法通过 Flask 来访问数据库,不能使用 Flask 的扩展功能等。如果想做到这些,Celery 都需要自己再实现一套相同的逻辑,这样做显然是没有必要的。所以 Flask application 原生支持将自己的 Context 嵌入到别的 application 中,当然有些情况也需要相应扩展的辅助,例如 Flask-Celery-Helper 在这里就充当着这个角色。

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

  • 使用工厂模式来创建 celery 对象
    ./celery_runner.py
mport osfrom celery import Celeryfrom jmilkfansblog import create_appdef 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.Taskclass ContextTask(TaskBase):abstract = Truedef __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 = ContextTaskreturn celeryenv = 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 = idself.email = textdef __repr__(self):return '<Model Reminder `{}`>'.format(self.text[:20])
  • 创建一个 task
    vim jmilkfansblog/tasks.py
import smtplib
import datetime
from email.mime.text import MIMETextfrom flask_mail import Messagefrom 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'] = <your_email>msg['To'] = reminder.emailtry:smtp_server = smtplib.SMTP('localhost')smtp_server.starttls()smtp_server.login(<user>, <password>)smtp_server.sendmail(<your_email>,[reminder.email],msg.as_string())smtp_server.close()returnexcept 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 eventdef 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 来负责传入.

用 Flask 来写个轻博客 (26) — 使用 Flask-Celery-Helper 实现异步任务相关推荐

  1. 用 Flask 来写个轻博客 (37) — 在 Github 上为第一阶段的版本打 Tag

    Blog 项目源码:https://github.com/JmilkFan/JmilkFan-s-Blog 目录 目录 前文列表 第一阶段结语 打 Tag 前文列表 用 Flask 来写个轻博客 (1 ...

  2. 用 Flask 来写个轻博客 (36) — 使用 Flask-RESTful 来构建 RESTful API 之五

    Blog 项目源码:https://github.com/JmilkFan/JmilkFan-s-Blog 目录 目录 前文列表 PUT 请求 DELETE 请求 测试 对一条已经存在的 posts ...

  3. 用 Flask 来写个轻博客 (35) — 使用 Flask-RESTful 来构建 RESTful API 之四

    Blog 项目源码:https://github.com/JmilkFan/JmilkFan-s-Blog 目录 目录 前文列表 POST 请求 身份认证 测试 前文列表 用 Flask 来写个轻博客 ...

  4. 用 Flask 来写个轻博客 (34) — 使用 Flask-RESTful 来构建 RESTful API 之三

    Blog 项目源码:https://github.com/JmilkFan/JmilkFan-s-Blog 目录 目录 前文列表 应用请求中的参数实现 API 分页 测试 前文列表 用 Flask 来 ...

  5. 用 Flask 来写个轻博客 (33) — 使用 Flask-RESTful 来构建 RESTful API 之二

    Blog 项目源码:https://github.com/JmilkFan/JmilkFan-s-Blog 目录 目录 前文列表 扩展阅读 构建 RESTful Flask API 定义资源路由 格式 ...

  6. 用 Flask 来写个轻博客 (32) — 使用 Flask-RESTful 来构建 RESTful API 之一

    Blog 项目源码:https://github.com/JmilkFan/JmilkFan-s-Blog 目录 目录 前文列表 扩展阅读 RESTful API REST 原则 无状态原则 面向资源 ...

  7. 用 Flask 来写个轻博客 (31) — 使用 Flask-Admin 实现 FileSystem 管理

    Blog 项目源码:https://github.com/JmilkFan/JmilkFan-s-Blog 目录 目录 前文列表 扩展阅读 编写 FileSystem Admin 页面 Flask-A ...

  8. 用 Flask 来写个轻博客 (30) — 使用 Flask-Admin 增强文章管理功能

    Blog 项目源码:https://github.com/JmilkFan/JmilkFan-s-Blog 目录 目录 前文列表 扩展阅读 实现文章管理功能 实现效果 前文列表 用 Flask 来写个 ...

  9. 用 Flask 来写个轻博客 (29) — 使用 Flask-Admin 实现后台管理 SQLAlchemy

    Blog 项目源码:https://github.com/JmilkFan/JmilkFan-s-Blog 目录 目录 前文列表 扩展阅读 Flask-Admin BaseView 基础管理页面 Mo ...

最新文章

  1. android findviewbyid定义成静态,findViewById 为null???
  2. javac compiling error ( mising package)
  3. Git push 时每次都需要密码的疑惑
  4. java servlet 获取mac地址_Java开发网 - 请教大家几个关于servlet取ip和mac地址的问题~~!...
  5. 读取和修改caffemodel文件里的参数
  6. 图表中如何实现动态变更分类轴与系列值
  7. html5 跳转参数不显示_TeeChart for PHP教程(十二):Javascript / HTML5图表
  8. android开发JNI之高级篇
  9. Html5简单描述(优点与缺点)
  10. jdbc sql拼接字符串
  11. 小明左右手分别拿两张纸牌:黑桃10和红心8,现在交换手中的牌。编写并输出互换后的结果,输出结果如图所示。
  12. 浊音、清音、爆破音的信号特性分析
  13. Elasticsearch1.x 拼音分词实现全拼首字母中文混合搜索
  14. 猫掉进山洞C语言随机数,《小猫出生在秘密山洞》读书心得最新范文五篇
  15. JAVA 系列——包装类
  16. linux wps 文件关联,WPS文件扩展名 - 什么是.wps以及如何打开? - ReviverSoft
  17. 通用表表达式实现 UPDATE/DELETE LIMIT
  18. 打印机 “错误-正在打印”
  19. 蓝桥杯2022年第十三届决赛真题-修路
  20. 2.12黑马培训日记

热门文章

  1. 在java中必须要有main吗_在一个Java应用程序中main方法必须被说明为_____。
  2. 怎么转换html格式文件怎么打开,html格式怎么转换
  3. native字体尺寸自适应 react_ReactNative字体大小不随系统字体大小变化而变化
  4. 脑与神经类开放数据库汇总
  5. HoloLens开发入门
  6. ios 裁剪框大小_iOS实现裁剪框和图片剪裁功能
  7. 名校华人教授专门设局诈骗中国留学生,4年吸金超百万美元,连亲戚也没放过...
  8. 2月19日2021CCF颁奖典礼即将举办,“为什么在横店?”
  9. 英特尔芯片专利官司败诉,判罚140亿,半个季度白干了...
  10. javascript之温习闭包