如何解决在每个时区每天凌晨 3:00 运行 celery 任务?姜戈
我需要每天凌晨 3:00 为多个时区运行一项任务,在应用中每个组织都有一个时区。
class Organization(models.Model):
timezone = models.IntegerField(choices=((i,i) for i in range(-12,13)))
因此,如果两个组织的时区不同,则任务应在各自时区的凌晨 3:00 执行,即不同时间。
我怎样才能做到这一点?
解决方法
对此的一种解决方案是:
- 列出所有必要的时区(例如
Asia/Manila
)及其相应的 UTC 偏移量(例如+8.0
)。这是 timezone names and their UTC offsets 的完整列表。 - 获取每个时区 03:00 AM 的 UTC 等效值
- 将 Celery 配置为基于 UTC,然后使用 celery beat 安排任务,这些任务将在每个时区的 03:00 AM 以 UTC 转换后运行
代码:
from dataclasses import dataclass
from datetime import datetime,time,timezone
from decimal import Decimal
import pytz # If using > Python3.9,you can try using the built-in library zoneinfo
### Step 1: Collect all the UTC offsets for all timezones
utc_offsets = set()
for tz in pytz.all_timezones:
# If needed,filter the timezones to only the ones that are necessary,skip those that are not needed.
dt = datetime.now(pytz.timezone(tz))
# Decimal was used instead of the built-in float for a more accurate precision
dt_utc_offset_seconds = Decimal(dt.utcoffset().total_seconds())
dt_utc_offset_minutes = dt_utc_offset_seconds / Decimal('60')
dt_utc_offset_hours = dt_utc_offset_minutes / Decimal('60')
utc_offsets.add(dt_utc_offset_hours)
### Step 2: Get the equivalent in UTC of each timezone's 03:00 AM
TASK_HOUR_TARGET = Decimal('3')
task_hour_per_tz = set()
for utc_offset in utc_offsets:
if utc_offset == 0:
task_hour_as_utc_offset = TASK_HOUR_TARGET
elif utc_offset > 0:
task_hour_as_utc_offset = TASK_HOUR_TARGET - utc_offset
if task_hour_as_utc_offset < 0:
task_hour_as_utc_offset = 24 - abs(task_hour_as_utc_offset)
elif utc_offset < 0:
task_hour_as_utc_offset = TASK_HOUR_TARGET + abs(utc_offset)
if task_hour_as_utc_offset >= 24:
task_hour_as_utc_offset = task_hour_as_utc_offset - 24
task_hour_per_tz.add((utc_offset,task_hour_as_utc_offset))
### Step 3: Schedule the execution of celery for each converted 03:00 AM of each timezone
@dataclass
class crontab:
# For the sake of testing,here is a mocked celery.schedules.crontab
minute: int
hour: int
# This assumes that celery_app.conf.timezone = "UTC"
beat_schedule = {
f"{utc_offset}": {
'task': 'task.func','schedule': crontab(
minute=int((task_hour * Decimal('60')) % Decimal('60')),hour=int(task_hour),),'kwargs': {"utc_offset": utc_offset},# To let the task know for what timezone is the trigger for
}
for utc_offset,task_hour in sorted(task_hour_per_tz) # No need of the sort. It is just here so that when we print the dictionary,the order is by utc_offset.
}
for key in beat_schedule:
print(key,beat_schedule[key])
输出:
-12 {'task': 'task.func','schedule': crontab(minute=0,hour=15),'kwargs': {'utc_offset': Decimal('-12')}}
-11 {'task': 'task.func',hour=14),'kwargs': {'utc_offset': Decimal('-11')}}
-10 {'task': 'task.func',hour=13),'kwargs': {'utc_offset': Decimal('-10')}}
-9.5 {'task': 'task.func','schedule': crontab(minute=30,hour=12),'kwargs': {'utc_offset': Decimal('-9.5')}}
-9 {'task': 'task.func','kwargs': {'utc_offset': Decimal('-9')}}
-8 {'task': 'task.func',hour=11),'kwargs': {'utc_offset': Decimal('-8')}}
-7 {'task': 'task.func',hour=10),'kwargs': {'utc_offset': Decimal('-7')}}
-6 {'task': 'task.func',hour=9),'kwargs': {'utc_offset': Decimal('-6')}}
-5 {'task': 'task.func',hour=8),'kwargs': {'utc_offset': Decimal('-5')}}
-4 {'task': 'task.func',hour=7),'kwargs': {'utc_offset': Decimal('-4')}}
-3 {'task': 'task.func',hour=6),'kwargs': {'utc_offset': Decimal('-3')}}
-2.5 {'task': 'task.func',hour=5),'kwargs': {'utc_offset': Decimal('-2.5')}}
-2 {'task': 'task.func','kwargs': {'utc_offset': Decimal('-2')}}
-1 {'task': 'task.func',hour=4),'kwargs': {'utc_offset': Decimal('-1')}}
0 {'task': 'task.func',hour=3),'kwargs': {'utc_offset': Decimal('0')}}
1 {'task': 'task.func',hour=2),'kwargs': {'utc_offset': Decimal('1')}}
2 {'task': 'task.func',hour=1),'kwargs': {'utc_offset': Decimal('2')}}
3 {'task': 'task.func',hour=0),'kwargs': {'utc_offset': Decimal('3')}}
4 {'task': 'task.func',hour=23),'kwargs': {'utc_offset': Decimal('4')}}
4.5 {'task': 'task.func',hour=22),'kwargs': {'utc_offset': Decimal('4.5')}}
5 {'task': 'task.func','kwargs': {'utc_offset': Decimal('5')}}
5.5 {'task': 'task.func',hour=21),'kwargs': {'utc_offset': Decimal('5.5')}}
5.75 {'task': 'task.func','schedule': crontab(minute=15,'kwargs': {'utc_offset': Decimal('5.75')}}
6 {'task': 'task.func','kwargs': {'utc_offset': Decimal('6')}}
6.5 {'task': 'task.func',hour=20),'kwargs': {'utc_offset': Decimal('6.5')}}
7 {'task': 'task.func','kwargs': {'utc_offset': Decimal('7')}}
8 {'task': 'task.func',hour=19),'kwargs': {'utc_offset': Decimal('8')}}
8.75 {'task': 'task.func',hour=18),'kwargs': {'utc_offset': Decimal('8.75')}}
9 {'task': 'task.func','kwargs': {'utc_offset': Decimal('9')}}
9.5 {'task': 'task.func',hour=17),'kwargs': {'utc_offset': Decimal('9.5')}}
10 {'task': 'task.func','kwargs': {'utc_offset': Decimal('10')}}
10.5 {'task': 'task.func',hour=16),'kwargs': {'utc_offset': Decimal('10.5')}}
11 {'task': 'task.func','kwargs': {'utc_offset': Decimal('11')}}
12 {'task': 'task.func','kwargs': {'utc_offset': Decimal('12')}}
12.75 {'task': 'task.func','kwargs': {'utc_offset': Decimal('12.75')}}
13 {'task': 'task.func','kwargs': {'utc_offset': Decimal('13')}}
14 {'task': 'task.func','kwargs': {'utc_offset': Decimal('14')}}
从输出中可以看出,每个时区都有自己的计划任务,这些任务将在 UTC 的 03:00 AM(例如 UTC+8.0 的 19:00)运行。
- 如果您不喜欢为每个时区生成单独的计划任务的想法,您可能需要配置一个更复杂的 crontab 例如
crontab(minute=0,hour='0,1,2,3,4...')
但我认为某些时区组合不可能使用不同的分钟,例如以满足 UTC+5.0 和 UTC+5.5 转换后的 03:00 AM,即分别为 22:00 和 21:30(第 0 分钟和第 30 分钟)。虽然您也可能只是为不同的分钟设置生成不同的计划任务,例如第 0 分钟、第 15 分钟、第 30 分钟和第 45 分钟。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。