Day 27 Celery

终於要进入 Celery 这个主题了,还记得我在 Day 24 说过介绍 Flask-Mail 的另一部分原因後面再说吗,结果好像就没有下文了,是因为 Celery 这个东西在介绍它之前要先讲一下 Redis ,然後就不小心讲了两篇了。

好了,回归正题 Celery 到底是什麽(绝对不是你认识的那个芹菜)?为什麽介绍 Flask-Mail 的一部分原因跟 Celery 有关?又为什麽要先讲完 Redis 才能开始介绍它呢?

首先 Celery 是一个由 Python 开发的分散式任务处理系统,能够将一些需要较长时间处里的任务交由其他机器运行,提高回应速度。

讲完了 Celery 是什麽之後,介绍 Flask-Mail 另一部分的原因想必你也在实做过它之後也就能够理解了吧(有吧,你应该有发现寄信超慢的吧)。

而先介绍 Redis 的原因当然是 Celery 会使用到 Redis 的功能啦。

详细介绍 Celery

再详细介绍之前先来看一张 Celery 的架构图。

从这张图我们可以看出来,Broker 就是放置待执行任务的地方, Backend 是放置任务完成後的结果的地方,而 Worker 就是执行任务的地方。看到这里你可能在想 Redis 呢?它在干嘛?为啥又需要它了?Redis 在 Celery 中主要是当作 Broker 跟 Backend 用的(废话,不然它还能当 Worker?)。

使用 Celery

开始使用之前,我们先来理解一下问题,最主要的问题就是 Flask-Mail 寄信太慢、太花时间,导致回应变慢。所以需要将处里的流程改成这样(图有点丑丑的不要介意):

所以要将寄信的部分包装成一个任务交由其他机器处理,让本机不会卡在寄信而回应变慢。当其他机器处理好之後再传出一个消息回应就可以了(也可以不传啦)。

好啦,大概理解了流程,就要来实做了,既然要使用到 Redis ,那还是回到我们的虚拟机吧。再开一个新的专案,并且把架构弄好,像这样:

ithomo_celery
├── base
│   └── __init__.py  # 初始化
├── templates
│   └── mail.html  # 寄信
├── tasks
│   ├── __init__.py  # 初始化
│   └── mail.py  # 寄信任务
├── app.py  # 主要的档案
├── config.py  # 设定档
├── Pipfile  # 不管它,建立虚拟环境时自己会出现
└── Pipfile.lock  # 不管它,安装套件时自己会出现

先来看一下比较不重要的 mail.html 吧。

<!DOCTYPE html>
<html>

<head>
    <meta charset="UTF-8" />
    <title>Send Mail</title>
</head>

<body>
	<div>
		<h1>{{ status }}</h1>
	</div>
    <h1>Hello</h1>
	<h2>Send Mail</h2>
	<form action="/mail" method="POST">
		<fieldset>
			<legend>Send mail</legend>
			<label>收件人</label><br />
			<input type="email" name="recipient" required /><br />
			<label>标题</label><br />
			<input type="text" name="title" /><br />
			<label>内容</label><br />
			<input type="text" name="content" /><br />
			<input type="submit" value="送出" />
		</fieldset>
	</form>
</body>

</html>

再来看一下突然出现、不太能从名字猜到是什麽的 base/__init__py

from flask import Flask
from flask_mail import Mail

import config


# templates 的位置记的修改一下
app = Flask(__name__, template_folder='../templates')
# 设定档里面不要忘记要设定有关 Email 的各种设定
app.config.from_object(config.DevelopmentConfig)

mail_app = Mail(app)

接着看 tasks 里面是甚麽

tasks/__init__.py

from celery import Celery
import time


# 这边我把 Backend 跟 Broker 分开主要是为了让我可以用 redis-cli 看 内容
# Backend 跟 Broker 也可以为同一个资料库
celery_app = Celery(
    __name__,
    backend = 'redis://localhost:6379/0',
    broker = 'redis://localhost:6379/1',
)

# 如果跟 Celery 实体不在同一个位置的话就必须进行 import
celery_app.conf['imports'] = ('tasks.mail', )

# 如果跟 Celery 实体在同一个位置的话则不必
@celery_app.task
def add(a, b):
    time.sleep(5)
    return a + b

tasks/mail.py

from flask_mail import Message

from base import app, mail_app
from tasks import celery_app


@celery_app.task
def send_mail(recipients, title, content):
    with app.app_context():
		msg = Message(title, recipients=[recipients])
        msg.body = content
        mail_app.send(msg)

最後再来看一下 app.py

from base import app
from flask import make_response, redirect, render_template, request, url_for

from tasks.mail import send_mail


@app.route('/mail', methods=['GET', 'POST'])
def mail():
    if request.method == 'GET':
        response = make_response(render_template('mail.html'))
    elif request.method == 'POST':
        recipient = request.values.get('recipient')
        title = request.values.get('title', '')
        content = request.values.get('content', '')

        ''' 直接寄信 '''
		# msg = Message(title, recipients=[recipient])
        # msg.body = content
        # mail_app.send(msg)
		
		''' 使用任务寄信 '''
		# delay 意思是发送一个任务出去,然後就不管结果了。
		send_mail.delay(recipients=recipient, title=title, content=content)
		
		# 如果想要等待结果可以使用 wait ,像这样
		# send = send_mail.delay(recipients=recipient, title=title, content=content)
		# send.wait()
        response = make_response(render_template('mail.html', status='Send success'))
    else:
        response = make_response(redirect(url_for('index')))

    return response


if __name__ == '__main__':
    app.run()

这样大概就可以了。接下来就可以分别启动 redis-server 、 Flask 以及你还不知道怎麽使用的 Worker(因为没有别台机器可以分担了,所以只好委屈一下你手上这台。还有记的是分 3 个 terminal 个别开喔)。

# 启动 redis-server
$ src/redis-server

# 启动 Flask
$ pipenv run python app.py

# 启动 Worker | -A 後面的参数是指向 Celery 的实例
$ pipenv run celery -A tasks.celery_app worker

然後就可以打开 http://localhost:5000/mail 测试一下了,你会发现回应的速度快了很多。如果要有个明确的数字证明真的有变快,不是心理作用,可以进行以下的操作。

$ pipenv install flask-debugtoolbar

base/__init__.py

from flask import Flask
from flask_mail import Mail
from flask_debugtoolbar import DebugToolbarExtension

import config


# templates 的位置记的修改一下
app = Flask(__name__, template_folder='../templates')
# 设定档里面不要忘记要设定有关 Email 的各种设定
app.config.from_object(config.DevelopmentConfig)

mail_app = Mail(app)

toolbar = DebugToolbarExtension(app)

然後再打开 http://localhost:5000/mail 的时候点一下左边的黑色框框,再点一下左下角的红色勾勾,就可以显示回应请求所费的时间了(如果想知道这是什麽,可以去看一下 flask-debugtoolbar ,简单的一个开发时 Debug 小工具而已)。

如果使用了 Celery 寄信时,回应请求所费时间大概在 300 毫秒左右。

如果不使用 Celery 寄信时,回应请求所费时间大概在 1800 毫秒左右。

由此可知的确减少了回应等待时间,就能够提高使用者体验了。

那麽就大概这样,Celery 算是简单的东西,不过设定的东西略为复杂,所以要细心地进行设定。

大家掰~掰~


<<:  Day27 用python写UI-聊聊Treeview(一)

>>:  Day27-TypeScript(TS)的命名空间(Namespace)与模组(Modules)

JavaScript Day 7. 浅谈 Function

自己常常在写程序的时候,因为习惯一种写法就很自然写下去,不太会去思考为什麽要这样用,就像每天早上都会...

【Day12】插槽 Portals

Portals 是一种让 children 可以 render 到 parent component...

30天轻松学会unity自制游戏-设定画面按钮

现在死亡後有了两个选项,一个重新开始游戏,一个是回到标题,目前只有一个场景,所以第一步快速制作一个开...

Day5 认识JSX,简化你的程序码

一般写程序的时候,我会将HTML和Javascript分开来写,但react提供了JSX的语法,将h...

伸缩自如的Flask [day 25] Flask with web cam

github: https://github.com/wilsonsujames/webcam/tr...