iT邦幫忙

2021 iThome 鐵人賽

DAY 27
0
自我挑戰組

月光下的Flask之旅系列 第 27

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 算是簡單的東西,不過設定的東西略為複雜,所以要細心地進行設定。

大家掰~掰~


上一篇
Day 26 Redis (下)
下一篇
Day 28 Flask-RESTX
系列文
月光下的Flask之旅30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言