# Celery_Periodic Tasks ###### tags: `celery` `celery 5.2` `python` [官方連結_Periodic Tasks](https://docs.celeryproject.org/en/stable/userguide/periodic-tasks.html) ## Periodic Tasks ### Introduction `celery beat`是一個排程器;它在定期的時間間隔中啟動任務,然後再由集群中的可用節點上執行這任務。 預設情況下,這些項目(entires)是由[beat_schedule](https://docs.celeryproject.org/en/stable/userguide/configuration.html#std-setting-beat_schedule)所設置,不過你也可以自定義,像是利用資料庫來保存這些項目之類的。 你必需確保一次只會有一個排程器在調度,否則你會遇到重複執行任務的問題。使用集中式的方法意味著不需要特別的去同步排程,而且服務可以在不使用鎖定(locking)的情況下運行。 ### Time Zones 預設情況下,週期性任務排程器使用UTC時區,你可以使用[timezone](https://docs.celeryproject.org/en/stable/userguide/configuration.html#std-setting-timezone)來調整時區設置。 下面給出一個時區為`Europe/Londan`的範例: ```python timezone = 'Europe/London' ``` 這個設置必需要加到你的應用程式中,不管是直接使用`(app.conf.timezone = 'Europe/London')`,或是使用`app.config_from_object`的話就可以加到你的配置模組裡面。更多細節請參閱[Configuration](https://docs.celeryproject.org/en/stable/getting-started/first-steps-with-celery.html#celerytut-configuration)。 預設的排程器(儲存排程在檔案`celerybeat-schedule`的資料)會自動偵測到時區的改變,所以只要你的時區有變更,排程器本身就會自己重新啟動,但其它的排程器可能就沒有那麼聰明(像是,Django資料庫排程器,見下說明),這種情況下,你必須手動重新啟動排程器。 #### Django Users Celery建議並相容Django 1.4中引入的新設置`USE_TZ`。 對Django用戶來說,你們將會使用`TIME_ZION`來設置時區,或是你可以使用[timezone](https://docs.celeryproject.org/en/stable/userguide/configuration.html#std-setting-timezone)單獨為Celery指定自定義時區。 資料庫排程器在時區調整之後並不會自動重啟,因此你應該手動執行: ```shell $ python manage.py shell >>> from djcelery.models import PeriodicTask >>> PeriodicTask.objects.update(last_run_at=None) ``` Django-Celery僅支援到Celery 4.0及更低版本,對於Celery 4.0及更高版本,請執行以下操作: ```python $ python manage.py shell >>> from django_celery_beat.models import PeriodicTask >>> PeriodicTask.objects.update(last_run_at=None) ``` ### Entries 如果你想要週期性的呼叫任務,你就需要新增一個項目(entry)到`beat schedule list`裡面。 ```python= from celery import Celery from celery.schedules import crontab app = Celery() @app.on_after_configure.connect def setup_periodic_tasks(sender, **kwargs): # Calls test('hello') every 10 seconds. sender.add_periodic_task(10.0, test.s('hello'), name='add every 10') # Calls test('world') every 30 seconds sender.add_periodic_task(30.0, test.s('world'), expires=10) # Executes every Monday morning at 7:30 a.m. sender.add_periodic_task( crontab(hour=7, minute=30, day_of_week=1), test.s('Happy Mondays!'), ) @app.task def test(arg): print(arg) @app.task def add(x, y): z = x + y print(z) ``` 從[on_after_configure](https://docs.celeryproject.org/en/stable/reference/celery.html#celery.Celery.on_after_configure)處理程序中設置這些週期性任務意味著在使用`test.s()`的時候我們不會在模組級別中評估應用程式。注意到,[on_after_configure](https://docs.celeryproject.org/en/stable/reference/celery.html#celery.Celery.on_after_configure)是在應用程式(app)設置之後才派送的,因此應用程式(app)所宣告的模組之外的任務(像是位於[celery.Celery.autodiscover_tasks()](https://docs.celeryproject.org/en/stable/reference/celery.html#celery.Celery.autodiscover_tasks)的`tasks.py`)就必需要使用later signal,像是[on_after_finalize](https://docs.celeryproject.org/en/stable/reference/celery.html#celery.Celery.on_after_finalize)。 函數[add_periodic_task()](https://docs.celeryproject.org/en/stable/reference/celery.html#celery.Celery.add_periodic_task)會在背景新增項目到[beat_schedule](https://docs.celeryproject.org/en/stable/userguide/configuration.html#std-setting-beat_schedule),相同的設置也可以用於手動設置週期性任務: 範例:每30秒執行一次`tasks.add`: ```python app.conf.beat_schedule = { 'add-every-30-seconds': { 'task': 'tasks.add', 'schedule': 30.0, 'args': (16, 16) }, } app.conf.timezone = 'UTC' ``` :::info **Note:** 如果你想要知道這些設置應該放那,請參考[Configuration](https://docs.celeryproject.org/en/stable/getting-started/first-steps-with-celery.html#celerytut-configuration)。你可以直接在你的應用程式上設置這些選項,也可以保留單獨一個模組來做配置。 如果你想要在`args`使用`single item tuple`,請不要忘記構造函數是逗號,而不是一對括弧。 ::: 你在排程中使用[timedelt](https://docs.python.org/dev/library/datetime.html#datetime.timedelta)意味著任務將會以每30秒的間隔派送(第一個任務會在`celery beat`啟動之後的30秒派送,接著每30秒派送)。 還有一個類似於排程器的`Crontab`,可參考下面[Crontab schedules](https://docs.celeryproject.org/en/stable/userguide/periodic-tasks.html#crontab-schedules)說明。 與`cron`一樣,如果上一個任務在下一個任務派出之前還沒完成,那就可能會造成任務的重疊。如果你會擔心這一點的話,那你應該使用鎖定策略來確保一次只會有一個實例可以執行。(參考範例[Ensuring a task is only executed one at a time](https://docs.celeryproject.org/en/stable/tutorials/task-cookbook.html#cookbook-task-serial)) #### Avaliable Fields * task * 預計執行的任務名稱 * schedule * 執行頻率 * 可以是整數<sub>(整數即代表幾秒)</sub>,[timedelta](https://docs.python.org/dev/library/datetime.html#datetime.timedelta),或[crontab](https://docs.celeryproject.org/en/stable/reference/celery.schedules.html#celery.schedules.crontab)。你也可以自定義排程類型,相關接口的部份請參考[schedule](https://docs.celeryproject.org/en/stable/reference/celery.schedules.html#celery.schedules.schedule) * args * Positional arguments (`list` or `tuple`) * kwargs * Keyword arguments (`dict`) * options * 執行選項(`dict`) * 可以是[apply_async()](https://docs.celeryproject.org/en/stable/reference/celery.app.task.html#celery.app.task.Task.apply_async)所支援的任意參數,`exchange`、`routing_key`、`expires` * relative * 如果`relative`為True,則[timedelta](https://docs.python.org/dev/library/datetime.html#datetime.timedelta)時間表"按時鐘"安排。這意味著頻率時間被四捨五入到最接近的秒、分鐘、小時或天,這取決於[timedelta](https://docs.python.org/dev/library/datetime.html#datetime.timedelta)設置的期間 * 預設情況下,`relative=False`,頻率的部份不會四捨五入,而是相對於celery beat開始的時間 ### <span id='corntab'>Crontab schedules</span> 如果你想要對任務執行的時候有更多控制,舉例來說,特定的時間或一週特定的日期,你可以使用[crontab](https://docs.celeryproject.org/en/stable/reference/celery.schedules.html#celery.schedules.crontab): ```python= from celery.schedules import crontab app.conf.beat_schedule = { # Executes every Monday morning at 7:30 a.m. 'add-every-monday-morning': { 'task': 'tasks.add', 'schedule': crontab(hour=7, minute=30, day_of_week=1), 'args': (16, 16), }, } ``` `Crontab`表達式的語法非常靈活。 幾個範例: | Example | Meaning | | -------- | -------- | | crontab() | 每分鐘執行 | |crontab(minute=0, hour=0) |每天午夜12點執行| |crontab(minute=0, hour='\*/3')|每三個小時執行: midnight, 3am, 6am, 9am, noon, 3pm, 6pm, 9pm| |crontab(minute='\*/15')|每十五分鐘執行| |crontab(day_of_week='sunday')|禮拜天的每分鐘執行| |crontab(minute='\*', hour='\*', day_of_week='sun')|禮拜天的每分鐘執行| |crontab(minute='\*/10', hour='3,17,22', day_of_week='thu,fri')|禮拜四、五的早上三點、下午五點、下午十點,每十分鐘執行| |crontab(minute=0, hour='\*/2,\*/3')|每隔一小時,時/3的時間,即1am, 5am, 7am, 11am, 1pm, 5pm, 7pm, 11pm除外| |crontab(minute=0, hour='\*/5')|時/5的時間執行,這意味執行時間是3點,非5點,因為3點在24小時制中是15,15/3=5| |crontab(minute=0, hour='\*/3,8-17')|時/3的時間以及早上8點至下午5點執行| |crontab(0, 0, day_of_month='2')|每個月第二天執行| |crontab(0, 0, day_of_month='2-30/2')|每個偶數日執行| |crontab(0, 0, day_of_month='1-7,15-21')|每個月的第一、第三週執行| |crontab(0, 0, day_of_month='11', month_of_year='5')|每年的五月十一日執行| |crontab(0, 0, month_of_year='\*/3')|每個季度第一個月的每日執行| 更多可參考[celery.schedules.crontab](https://docs.celeryproject.org/en/stable/reference/celery.schedules.html#celery.schedules.crontab)說明 ### Solar schedules 如果你的任務需要根據日出、日落、黎明或黃昏來執行,你可以使用[solar](https://docs.celeryproject.org/en/stable/reference/celery.schedules.html#celery.schedules.solar): ```python from celery.schedules import solar app.conf.beat_schedule = { # Executes at sunset in Melbourne 'add-at-melbourne-sunset': { 'task': 'tasks.add', 'schedule': solar('sunset', -37.81753, 144.96715), 'args': (16, 16), }, } ``` 參數非常簡單:`solor(事件, 緯度, 經度)`。 請務必使用正確的經緯符號: | Sign | Argument | Meaning | |------|-----------|---------| | + | latitude | North | | - | latitude | South | | + | longitude | East | | - | longitude | West | 可能有的事件類型如下: |Event | Meaning| |------|-----------| |dawn_astronomical|在天空不再完全黑暗,黎明中出現一道署光的那一刻執行。這時候太陽是在地平線上18度的時間| |dawn_nautical|Execute when there’s enough sunlight for the horizon and some objects to be distinguishable; formally, when the sun is 12 degrees below the horizon.| |dawn_civil|Execute when there’s enough light for objects to be distinguishable so that outdoor activities can commence; formally, when the Sun is 6 degrees below the horizon.| |sunrise|Execute when the upper edge of the sun appears over the eastern horizon in the morning.| |solar_noon|當天太陽已經升到最高的時候執行| |sunset|Execute when the trailing edge of the sun disappears over the western horizon in the evening.| |dusk_civil|Execute at the end of civil twilight, when objects are still distinguishable and some stars and planets are visible. Formally, when the sun is 6 degrees below the horizon.| |dusk_nautical|Execute when the sun is 12 degrees below the horizon. Objects are no longer distinguishable, and the horizon is no longer visible to the naked eye.| |dusk_astronomical|Execute at the moment after which the sky becomes completely dark; formally, when the sun is 18 degrees below the horizon.| `solar`所有的事件都是使用UTC計算,因此不受時區設置影響。 在極地區域,也許不會每天都有日出或日落。排程器可以處理這些狀況(例如,如果當天沒有日出,那日出的事件(sunrise event)在當天就不會執行)。唯一的例外是`solor_noon`,它被正式定義為太陽經過天體子午線的那一刻,即使太陽低於地平線也會每天發生。 曙暮光被定義為介於黎明與日出之間;在日落與黃昏之間。你可以根據你對曙暮光的定義來排程(民間、航海或天文)以及你是否希望事件在黃昏的開始或結束時進行,使用上面清單中的適合事件。 更多資訊請參考[celery.schedules.solar](https://docs.celeryproject.org/en/stable/reference/celery.schedules.html#celery.schedules.solar) :::success 譯者註:這邊天文的太專業了,直接取機器翻譯。 ::: ### Starting the Scheduler 啟動`celery beat`服務方式如下指令 ```shell $ celery -A proj beat ``` 你也可以透過設置選項`worker -B`將beat崁入worker,對於不會有多個worker節點的情況來說是方便的,不過不常用,因此不建議用於生產環境: ```shell $ celery -A proj worker -B ``` beat需要一個local端的文件來保存最後執行記錄,名稱預設為`celerybeat-schedule`,當然你也可以指定,也要記得設置好相對應文件目錄的寫入權限: ```shell $ celery -A proj beat -s /home/celery/var/run/celerybeat-schedule ``` :::info **Note:** 如果有背景執行需求的話,請參閱[Daemonization](https://docs.celeryproject.org/en/stable/userguide/daemonizing.html#daemonizing) ::: #### Using custom scheduler classes 你可以在命令列中利用(['--scheduler'](https://docs.celeryproject.org/en/stable/reference/cli.html#cmdoption-celery-beat-S))來指定自定義排程器類別。 預設的排程器為[celery.beat.PersistenScheduler](https://docs.celeryproject.org/en/stable/reference/celery.beat.html#celery.beat.PersistentScheduler),它單純的在本地端[shelve](https://docs.python.org/dev/library/shelve.html#module-shelve)資料庫檔案中保存最後的的執行時間。 另外還有[django-celery-beat](https://pypi.org/project/django-celery-beat/)的擴展,它會把排程保存在Django資料庫中,提供方便的管理者接口來管理周期性任務。 安裝使用該擴展: 1. 使用pip安裝該模組: ```shell $ pip install django-celery-beat ``` 2. 在Django專案的`settings.py`中新增`django_celery_beat`模組到`INSTALLED_APPS` ```python INSTALLED_APPS = ( ..., 'django_celery_beat', ) ``` 注意到,沒有`-`,只有底線`_` 3. 執行Django資料庫轉移,以便建立需求資料表: ```shell $ python manage.py migrate ``` 4. 起動`celery beat`服務 ```shell $ celery -A proj beat -l INFO --scheduler django_celery_beat.schedulers:DatabaseScheduler ``` 注意:你也可以直接地把它加入做為[beat_scheduler](https://docs.celeryproject.org/en/stable/userguide/configuration.html#std-setting-beat_scheduler)的設置。 5. 至`Django-Admin`界面設置一些周期性任務。 ## History 20190725_依據4.3版本說明調整 20211217_依據5.2版本說明調整