定時任務、Celery、消息隊列

python定時任務有以下常見方案

注意gunicorn多worker時可能導致任務重復執行,可使用redis_lock等分布式鎖、celery beat 、 gunicorn的preload=True 配置等解決

  • python-crontab 系列
    如python-crontab、django-crontab
    封裝了Linux提供的crontab命令
    在Linux上需開啟crontab,不支持windows,適用于中小型項目
  • apscheduler 系列
    如apscheduler、django-apscheduler、flask-apscheduler
    支持windows和linux,適用于中小型項目
  • Celery 系列
    如celery、django-celery、flask-celery
    支持windows和linux,支持分布式,配置較復雜,適用于大型項目
  • 自建輪子
import os, sys, time, datetime
import threading
import django
base_apth = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
# print(base_apth)
# 將項目路徑加入到系統path中,這樣在導入模型等模塊時就不會報模塊找不到了
sys.path.append(base_apth)
os.environ['DJANGO_SETTINGS_MODULE'] ='base_django_api.settings' # 注意:base_django_api 是我的模塊名,你在使用時需要跟換為你的模塊
django.setup()
from base.models import ConfDict

def confdict_handle():
    while True:
        try:
            loca_time = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
            print('本地時間:'+str(loca_time))
            time.sleep(10)
        except Exception as e:
            print('發生錯誤,錯誤信息為:', e)
            continue


def main():
    '''
    主函數,用于啟動所有定時任務,因為當前定時任務是手動實現,因此可以自由發揮
    '''
    try:
        # 啟動定時任務,多個任務時,使用多線程
        task1 = threading.Thread(target=confdict_handle)
        task1.start()
    except Exception as e:
        print('發生異常:%s' % str(e))

if __name__ == '__main__':
    main()

django-crontab

安裝及配置
  1. 安裝
pip install django-crontab
  1. 注冊
INSTALLED_APPS = (
    'django_crontab',
    ...
)
  1. settings.py 配置
    (時間配置, 'app名.文件名.函數名', 位置參數, 關鍵字參數, '>> 輸出文件路徑和名稱')
  • 位置參數和關鍵字參數要么都填要么都不填
  • log需通過print輸出
  • 配置的方法如不是view類型,django數據庫指令不會自動提交,需配合with transaction.atomic()事務
CRONJOBS = [
    # 每隔5分鐘運行一次
    ('*/5 * * * *', 'myapp.crontab.my_scheduled_job'),
    # 每隔6小時運行一次
    ('*/360 * * * *', 'weimob.views.refreshToken', ['James'], {}, '>> /tmp/django-crontab.log')
]

CRONTAB_COMMAND_PREFIX = 'LANG_ALL=zh_cn.UTF-8' # 解決 crontab 中文問題
  1. 常用命令
python manage.py crontab add # 添加所有django-crontab任務到系統crontab
python manage.py crontab show # 查看django-crontab添加過的任務
python manage.py crontab remove # 清除django-crontab任務
crontab -l # 查看所有系統crontab任務

函數內改動無需重新add,因每次調用都是一次獨立行為(沒有緩存)。
CRONJOBS改動后需要重新add,因每次add生成的hash校驗會通不過。


Celery

Celery是一個任務隊列管理工具,可用于實現異步接口、定期刪除/緩存Redis數據、定期發送消息等。Celery本身不提供消息存儲。

  • Producer
    生產者,調用Celery的API產生任務并交給任務隊列
  • Celery Beat
    任務調度器,Beat進程會讀取配置文件的內容,周期性地將配置中到期需要執行的任務發送給任務隊列。
  • Brokers
    中間人,指任務隊列,僅支持Redis、RabbitMQ。
  • Celery Workers
    消費者,從隊列中取出任務并執行。可在多臺服務器運行多個消費者提高效率
  • Result Stores / backend
    任務處理完后保存狀態信息和結果,可以使用Redis、RabbitMQ或DjangoORM等。


    celery工作流程

消息隊列

使用場景
  1. 解耦
    如在訂單與庫存系統中加入消息隊列,使兩個系統解耦
  2. 異步任務
    如發送短信、郵件、刷新緩存等
  3. 流量削峰
    如秒殺活動等高并發場景
broker選擇
  • Redis
    其list適用于做輕量級的MQ存儲,但功能和邏輯需上層應用自行實現
  • RabbitMQ
    使用生產-消費者模式,并引入了Echange(交換器)概念,根據調度策略將生產者的消息轉發給符合的Queue,實現解耦

相比于redis的優勢:

  1. 發布確認:生產者發布消息給Broker后,會收到Broker的反饋,保證發布成功
  2. 消費確認:消息提交給消費者后,如未成功消費確認,會返回到消息隊列
  3. 高可用性:自帶集群和負載均衡
  4. 持久化:redis只能將整個數據庫持久化,而RabbitMQ可以對每條隊列或消息分別設置持久化
基本使用DEMO
  1. 安裝redis和celery
    如果celery>=4.0,需要確保redis>=2.10.4
apt-get install redis-server
pip install redis
pip install celery
  1. 建立task
#tasks.py
from celery import Celery
 
app = Celery('tasks',  backend='redis://:yourpassword@localhost:6379/0', broker='redis://:yourpassword@localhost:6379/0') #配置好celery的backend和broker
 
@app.task  #普通函數裝飾為 celery task
def add(x, y):
    return x + y

也可以通過app.config_from_object() 加載配置模塊:

app.config_from_object('celeryconfig')

# celeryconfig.py
broker_url = 'pyamqp://'
result_backend = 'rpc://'

task_serializer = 'json'
result_serializer = 'json'
accept_content = ['json']
timezone = 'Europe/Oslo'
enable_utc = True
  1. 啟動worker(開始從隊列中讀取任務并執行)
celery -A tasks worker --loglevel=info

可通過celery worker --help查看命令列表,常用包括以下內容:

  • -A, --app
    指定使用的 Celery 實例,必須為module.path:attribute格式,如-A swallow.celery
  • -l, --loglevel [DEBUG(默認)|INFO|WARNING|ERROR|CRITICAL|FATAL]
    日志級別
  • -P, --pool [prefork(默認)|eventlet|gevent|solo|processes|threads]
    并發模式,其默認值prefork在windows上不支持,會報錯not enough values to unpack (expected 3, got 0)
    可使用--pool=solo(單進程)或-P solo代替
    也可以使用geventeventlet,但需先用pip下載
  • -c, --concurrency
    同時處理任務的工作進程數量,超出的任務需等待其他任務完成后執行。默認為CPU數
  1. 觸發任務
  • 通過delayapply_async直接觸發
    當任務完成時result.ready() 為 True,然后用 result.get() 取結果即可。
    確保調用了 result.get()result.forget(),否則資源不會釋放
#trigger.py
from tasks import add
result = add.delay(4, 4) #不要直接 add(4, 4),這里需要用 celery 提供的接口 delay 進行調用
while not result.ready():
    time.sleep(1)
print 'task done: {0}'.format(result.get())
  • 通過beat定時觸發
    celery beat 是一個調度程序,會定期觸發任務
    通過celery -A 【beat實例】 beat啟動,如celery -A swallow.celery beat -l info
    此處的crontab模塊并未調用系統的crontab,只是同名罷了
#settings.py
from celery.schedules import timedelta, crontab
# 默認使用UTC時區,建議改為`'Asia/Shanghai'`
CELERY_TIMEZONE = 'Asia/Shanghai'
CELERYBEAT_SCHEDULE = {
    "task_every_30_minutes": {# 該key隨便起,不需要調用。
        "task": "task_every_30_minutes",# 實際調用的是這個task
        "schedule": timedelta(minutes=30)
    },
    "task_every_day_start": {
        "task": "task_every_day_start",
        "schedule": crontab(minute=0, hour=0)
    },
    "task_every_even_hour": {
        "task": "task_every_even_hour",
        'schedule': crontab(minute=0, hour='0,2,4,6,8,10,12,14,16,18,20,22'),
    },
}

在django中使用celery
  • 獨立使用celery
    需進行相關配置,使celery可以調用django中的內容,如django.setup()等。
  • 使用celery + djcelery
    配置更方便
內存泄漏問題
  1. 不要配合django的settings.DEBUG=True使用,
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,739評論 6 534
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,634評論 3 419
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 176,653評論 0 377
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,063評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,835評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,235評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,315評論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,459評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,000評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,819評論 3 355
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,004評論 1 370
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,560評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,257評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,676評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,937評論 1 288
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,717評論 3 393
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,003評論 2 374

推薦閱讀更多精彩內容