在 Flask 項(xiàng)目中使用 Celery(下)

注意,這篇 Blog 嚴(yán)重參考了這兩篇文章:

  1. Using Celery With Flask: 寫了一個(gè)完整而且有意義的例子來展示如何在 Flask 中使用 Celery.
  2. Celery and the Flask Application Factory Pattern: 是上文的姊妹篇,描述的是更為真實(shí)的場(chǎng)景下,Celery 與 Flask Application Factory的結(jié)合使用伟葫。

Minimum Example
Celery 的一些設(shè)計(jì)和概念咙崎,與 Flask 很像弟跑,在 Flask 項(xiàng)目中集成 Celery 也很簡(jiǎn)單廊蜒,不像 Django 或其他框架需要擴(kuò)展插件。首先來看個(gè)最簡(jiǎn)單的例子 example.py:

import uuid
from flask import Flask, request, jsonify
from celery import Celery

app = Flask(__name__)
app.config['CELERY_BROKER_URL'] = 'redis://localhost:6379/0'
app.config['CELERY_RESULT_BACKEND'] = 'redis://localhost:6379/0'

celery = Celery(app.name,broker=app.config['CELERY_BROKER_URL'])
celery.conf.update(app.config) 

@celery.task 
def send_email(to, subject, content):
    return do_send_email(to, subject, content)

@app.route('/password/forgot/', methods=['POST'])
def reset_password(): 
    email = request.form['email']
    token = str(uuid.uuid4()) 
    content = u'請(qǐng)點(diǎn)擊鏈接重置密碼:http://example.com/password/reset/?token=%s' % token   
    send_email.delay(email, content) 
    return jsonify(code=0, message=u'發(fā)送成功')

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

啟動(dòng) Celery worker:

$ celery worker -A example.celery -l INFO

啟動(dòng) Web server:

 $ python example.py

當(dāng)然胯盯,實(shí)際應(yīng)用在生產(chǎn)環(huán)境下懈费,不能直接用 Flask 自帶的 server,需要使用 Gunicorn 這樣的 WSGI 容器博脑,或 uWSGI. 而且 Celery worker 進(jìn)程和 Web server 進(jìn)程應(yīng)該用 supervisord 管理起來憎乙。

Becoming Bigger
這是個(gè)最簡(jiǎn)單的例子,實(shí)際應(yīng)用會(huì)比這個(gè)復(fù)雜很多:有很多模塊叉趣,更復(fù)雜的配置泞边,更多的 task 等。在這種情況下疗杉,F(xiàn)lask 推薦使用 Application Factory Pattern阵谚,也就是定義一個(gè) function,在這里創(chuàng)建 Flask app 對(duì)象,并且處理注冊(cè)路由(blueprints)梢什、配置 logging 等一系列初始化操作奠蹬。
下面我們看看在更大的 Flask 項(xiàng)目里,應(yīng)該如何使用 Celery.
項(xiàng)目結(jié)構(gòu)

首先來看一下整個(gè)項(xiàng)目的結(jié)構(gòu):

├── README.md
├── app
│   ├── __init__.py
│   ├── config.py
│   ├── forms
│   ├── models
│   ├── tasks
│   │   ├── __init__.py
│   │   └── email.py
│   └── views
│   │   ├── __init__.py
│   │   └── account.py
├── celery_worker.py
├── manage.py└── wsgi.py

這個(gè)圖里省略了很多細(xì)節(jié)嗡午,簡(jiǎn)單解釋一下:
項(xiàng)目的根目錄下囤躁,有個(gè)celery_worker.py的文件,這個(gè)文件的作用類似于wsgi.py翼馆,是啟動(dòng) Celery worker 的入口割以。
app 包里是主要業(yè)務(wù)代碼金度,其中 tasks 里定義里一系列的 task应媚,提供給其他模塊調(diào)用。

主要代碼猜极。

  • app/config.py
class BaseConfig(object):
CELERY_BROKER_URL = 'redis://localhost:6379/2' 
CELERY_RESULT_BACKEND = 'redis://localhost:6379/2' 
CELERY_TASK_SERIALIZER = 'json'

BaseConfig是整個(gè)項(xiàng)目用到的配置的基類中姜,實(shí)際上還會(huì)派生出DevelopmentConfig,StagingConfig和ProductionConfig
等類。這里不討論配置的細(xì)節(jié)跟伏,也只關(guān)心和 Celery 相關(guān)的配置項(xiàng)丢胚。

  • app/init.py
from celery import Celery
from flask import Flask
from app.config import BaseConfig
celery = Celery(__name__,broker=BaseConfig.CELERY_BROKER_URL)
def create_app(): 
      app = Flask(__name__) 
      celery.conf.update(app.config)    # 更新 celery 的配置 
      return app
  • app/tasks/email.py
from flask import current_app
from celery.util.log import get_task_logger
from app import celery
logger = get_task_logger(__name__)
@celery.task
def send_email(to, subject, content):
      app = current_app._get_current_object()
      subject = app.config['EMAIL_SUBJECT_PREFIX'] + subject            
      logger.info('send message "%s" to %s', content, to) 
     return do_send_email(to, subject, content)
  • app/views/account.py
import uuid
from flask import Blueprint, request,jsonify
from app.tasks.email import send_email
bp_account = Blueprint('account', __name__)
@bp_account.route('/password/forgot/', methods=['POST'])
def reset_password(): 
      email = request.form['email'] 
      token = str(uuid.uuid4()) 
      content = u'請(qǐng)點(diǎn)擊鏈接重置密碼:http://example.com/password/reset/?token=%s' % token 
      send_email.delay(email, content)
      return jsonify(code=0, message=u'發(fā)送成功')
  • ceelry_worker.py
from app import create_app, celery
app = create_app()
app.app_context().push()

這個(gè)celery_worker.py文件有兩個(gè)操作:創(chuàng)建一個(gè) Flask 實(shí)例推入 Flask application context

第一個(gè)操作很簡(jiǎn)單,其實(shí)也是初始化了 celery 實(shí)例受扳。

第二個(gè)操作看起來有些奇怪携龟,實(shí)際上也很好理解。如果用過 Flask 就應(yīng)該知道 Flask 的 Application ContextRequest Context. Flask 一個(gè)很重要的設(shè)計(jì)理念是:在一個(gè) Python 進(jìn)程里可以運(yùn)行多個(gè)應(yīng)用(application)勘高,當(dāng)存在多個(gè) application 時(shí)可以通過current_app獲取當(dāng)前請(qǐng)求所對(duì)應(yīng)的 application.current_app綁定的是當(dāng)前 request 的 application 的引用峡蟋,在非 request-response 環(huán)境里,是沒有request context 的华望,所以調(diào)用current_app就會(huì)拋出異常(RuntimeError: working outside of application context)蕊蝗。創(chuàng)建一個(gè) request context 沒有必要,而且消耗資源赖舟,所以就引入了 application context.app.app_context().push()會(huì)推入一個(gè) application context蓬戚,后續(xù)所有操作都會(huì)在這個(gè)環(huán)境里執(zhí)行,直到進(jìn)程退出宾抓。因此子漩,如果在 tasks 里用到了current_app或其它需要 application context 的東西,就一定需要這樣做石洗。(默認(rèn)情況下 Celery 的 pool 是 prefork幢泼,也就是多進(jìn)程,現(xiàn)在這種寫法沒有問題劲腿;但是如果指定使用 gevent旭绒,是沒用的。這種情況下有別的解決方案,以后會(huì)寫文章討論挥吵。)

運(yùn)行
在項(xiàng)目的根路徑下啟動(dòng) Celery worker:

$ celery worker -A celery_worker.celery -l INFO

總結(jié)
上面兩個(gè)例子重父,實(shí)際上主要的差別就是初始化方式和模塊化,還有需要注意 Flask 的 application context 問題忽匈。文章內(nèi)容比較簡(jiǎn)單房午,文中的一些鏈接是很好的擴(kuò)展和補(bǔ)充,值得一看
丹允。

原文鏈接:http://www.tuicool.com/articles/UziEN3I

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末郭厌,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子雕蔽,更是在濱河造成了極大的恐慌折柠,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,718評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件批狐,死亡現(xiàn)場(chǎng)離奇詭異扇售,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)嚣艇,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,683評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門承冰,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人食零,你說我怎么就攤上這事困乒。” “怎么了贰谣?”我有些...
    開封第一講書人閱讀 158,207評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵娜搂,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我冈爹,道長(zhǎng)涌攻,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,755評(píng)論 1 284
  • 正文 為了忘掉前任频伤,我火速辦了婚禮恳谎,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘憋肖。我一直安慰自己因痛,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,862評(píng)論 6 386
  • 文/花漫 我一把揭開白布岸更。 她就那樣靜靜地躺著鸵膏,像睡著了一般。 火紅的嫁衣襯著肌膚如雪怎炊。 梳的紋絲不亂的頭發(fā)上谭企,一...
    開封第一講書人閱讀 50,050評(píng)論 1 291
  • 那天廓译,我揣著相機(jī)與錄音,去河邊找鬼债查。 笑死非区,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的盹廷。 我是一名探鬼主播征绸,決...
    沈念sama閱讀 39,136評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼俄占!你這毒婦竟也來了管怠?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,882評(píng)論 0 268
  • 序言:老撾萬榮一對(duì)情侶失蹤缸榄,失蹤者是張志新(化名)和其女友劉穎渤弛,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體碰凶,經(jīng)...
    沈念sama閱讀 44,330評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡暮芭,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,651評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了欲低。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,789評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡畜晰,死狀恐怖砾莱,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情凄鼻,我是刑警寧澤腊瑟,帶...
    沈念sama閱讀 34,477評(píng)論 4 333
  • 正文 年R本政府宣布,位于F島的核電站块蚌,受9級(jí)特大地震影響闰非,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜峭范,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,135評(píng)論 3 317
  • 文/蒙蒙 一财松、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧纱控,春花似錦辆毡、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,864評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至尔店,卻和暖如春眨攘,著一層夾襖步出監(jiān)牢的瞬間主慰,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,099評(píng)論 1 267
  • 我被黑心中介騙來泰國打工鲫售, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留河哑,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,598評(píng)論 2 362
  • 正文 我出身青樓龟虎,卻偏偏與公主長(zhǎng)得像璃谨,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子鲤妥,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,697評(píng)論 2 351

推薦閱讀更多精彩內(nèi)容

  • 22年12月更新:個(gè)人網(wǎng)站關(guān)停佳吞,如果仍舊對(duì)舊教程有興趣參考 Github 的markdown內(nèi)容[https://...
    tangyefei閱讀 35,170評(píng)論 22 257
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn)棉安,斷路器底扳,智...
    卡卡羅2017閱讀 134,638評(píng)論 18 139
  • 最近項(xiàng)目中要實(shí)現(xiàn)消息推送功能,用推送消息的方式在商品下單的時(shí)候通知貨源發(fā)布者贡耽,后端實(shí)現(xiàn)是 ==python== 衷模,...
    西北望高樓閱讀 987評(píng)論 0 0
  • [TOC]一直想做源碼閱讀這件事,總感覺難度太高時(shí)間太少蒲赂,可望不可見阱冶。最近正好時(shí)間充裕,決定試試做一下滥嘴,并記錄一下...
    何柯君閱讀 7,177評(píng)論 3 98
  • PLEASE READ THE FOLLOWING APPLE DEVELOPER PROGRAM LICENSE...
    念念不忘的閱讀 13,447評(píng)論 5 6