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
安裝及配置
- 安裝
pip install django-crontab
- 注冊
INSTALLED_APPS = (
'django_crontab',
...
)
- 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 中文問題
- 常用命令
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工作流程
消息隊列
使用場景
- 解耦
如在訂單與庫存系統中加入消息隊列,使兩個系統解耦 - 異步任務
如發送短信、郵件、刷新緩存等 - 流量削峰
如秒殺活動等高并發場景
broker選擇
- Redis
其list適用于做輕量級的MQ存儲,但功能和邏輯需上層應用自行實現 - RabbitMQ
使用生產-消費者模式,并引入了Echange(交換器)概念,根據調度策略將生產者的消息轉發給符合的Queue,實現解耦
相比于redis的優勢:
- 發布確認:生產者發布消息給Broker后,會收到Broker的反饋,保證發布成功
- 消費確認:消息提交給消費者后,如未成功消費確認,會返回到消息隊列
- 高可用性:自帶集群和負載均衡
- 持久化:redis只能將整個數據庫持久化,而RabbitMQ可以對每條隊列或消息分別設置持久化
基本使用DEMO
- 安裝redis和celery
如果celery>=4.0,需要確保redis>=2.10.4
apt-get install redis-server
pip install redis
pip install celery
- 建立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
- 啟動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
代替
也可以使用gevent
或eventlet
,但需先用pip下載 - -c, --concurrency
同時處理任務的工作進程數量,超出的任務需等待其他任務完成后執行。默認為CPU數
- 觸發任務
- 通過
delay
或apply_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
配置更方便
內存泄漏問題
- 不要配合django的settings.DEBUG=True使用,