終於要進入 Celery 這個主題了,還記得我在 Day 24 說過介紹 Flask-Mail 的另一部分原因後面再說嗎,結果好像就沒有下文了,是因為 Celery 這個東西在介紹它之前要先講一下 Redis ,然後就不小心講了兩篇了。
好了,回歸正題 Celery 到底是什麼(絕對不是你認識的那個芹菜)?為什麼介紹 Flask-Mail 的一部分原因跟 Celery 有關?又為什麼要先講完 Redis 才能開始介紹它呢?
首先 Celery 是一個由 Python 開發的分散式任務處理系統,能夠將一些需要較長時間處裡的任務交由其他機器運行,提高回應速度。
講完了 Celery 是什麼之後,介紹 Flask-Mail 另一部分的原因想必你也在實做過它之後也就能夠理解了吧(有吧,你應該有發現寄信超慢的吧)。
而先介紹 Redis 的原因當然是 Celery 會使用到 Redis 的功能啦。
再詳細介紹之前先來看一張 Celery 的架構圖。
從這張圖我們可以看出來,Broker 就是放置待執行任務的地方, Backend 是放置任務完成後的結果的地方,而 Worker 就是執行任務的地方。看到這裡你可能在想 Redis 呢?它在幹嘛?為啥又需要它了?Redis 在 Celery 中主要是當作 Broker 跟 Backend 用的(廢話,不然它還能當 Worker?)。
開始使用之前,我們先來理解一下問題,最主要的問題就是 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 算是簡單的東西,不過設定的東西略為複雜,所以要細心地進行設定。
大家掰~掰~