Celery 定时任务时区问题

9 投票
3 回答
23109 浏览
提问于 2025-04-18 00:50

我在一个服务器上使用celery,现在服务器的时间是英国夏令时(BST),结果我的定时任务突然提前了一小时执行!之前服务器的时间是欧洲/伦敦,也就是格林威治标准时间(GMT),但现在因为夏令时的原因变成了BST(GMT + 1)。

我已经把celery配置成使用这样的时区:

CELERY_TIMEZONE = 'Europe/London'

然后在调用任务时,我也把eta参数的值设置为'欧洲/伦敦',像这样:

from datetime import datetime
from pytz import timezone

locale_to_use = timezone('Europe/London')
current_time = locale_to_use.localize(datetime.now())

并在调用任务时,把这个current_time作为eta参数的值。

现在我是不是在设置eta参数值时犯了什么错误? 我的服务器是BST。

在夏令时生效之前,这个配置没有出现任何问题!

编辑:

为了更清楚,我在这里贴出我的代码示例:

@app.task(ignore_result=True)
def eta_test():

    logger.info('Executing eta_test on {0}'.format(datetime.now()))


def run_eta_test(hour, minute):

    now_time = datetime.now()
    target_time = now_time.replace(hour=hour, minute=minute, second=0)

    from settings import options
    from pytz import timezone

    local_zone = timezone('Europe/London')

    target_time = local_zone.localize(target_time)

    eta_test.apply_async(eta=target_time)

然后我在服务器的python控制台中调用run_eta_test,像这样:

test_tasks.run_eta_test(17, 17)

也就是说,想在当天的17:17:00执行任务。服务器的时间是17:15:45,但它没有在2秒后安排任务,而是立即执行了:

[2014-04-04 17:15:45,341: INFO/MainProcess] Received task: scheduling.test_tasks.eta_test[c28448d6-3a51-42f7-9df2-cb93385ff7c6] eta:[2014-04-04 17:17:00.095001+01:00]
[2014-04-04 17:15:46,820: INFO/Worker-3] Executing eta_test on 2014-04-04 17:15:46.820316
[2014-04-04 17:15:46,820: INFO/MainProcess] Task scheduling.test_tasks.eta_test[c28448d6-3a51-42f7-9df2-cb93385ff7c6] succeeded in 0.0008487419690936804s: None

然后我再次调用任务,想让它在一小时后几秒钟执行,调用方式是:

test_tasks.run_eta_test(18, 17)

结果它没有安排在一小时后几秒钟,而是只在几秒后执行,也就是提前了一小时:

[2014-04-04 17:16:27,703: INFO/MainProcess] Received task: scheduling.test_tasks.eta_test[f1a54d08-c12d-457f-bee8-04ca35b32242] eta:[2014-04-04 18:17:00.700327+01:00]
[2014-04-04 17:17:01,846: INFO/Worker-2] Executing eta_test on 2014-04-04 17:17:01.846561
[2014-04-04 17:17:01,847: INFO/MainProcess] Task scheduling.test_tasks.eta_test[f1a54d08-c12d-457f-bee8-04ca35b32242] succeeded in 0.0012819559779018164s: None

我的服务器日期配置为BST,像这样:

Fri Apr  4 17:29:10 BST 2014

现在,服务器的时区是不是成了问题?

再次编辑:

我没有通过回答和评论解决这个问题。所以我做的是,使用了CELERY_ENABLE_UTC = False,并且完全没有使用CELERY_ENABLE_UTC的值。然后,我直接使用服务器时间,没有任何时区设置。celery似乎能正确地在我的服务器时间下安排任务。

3 个回答

0

我觉得很奇怪,如果我们设置了celery的时区,它会把预计到达时间(eta)调整为当地时间加上UTC时间。但是,celery的预计到达时间似乎是根据UTC时间来触发的。现在的UTC时间是时间加上celery的时区,这样就不对了。

我猜@bgschiller使用UTC作为默认时区是一种解决办法……

def celery_localtime_util(t):
    bj_tz = pytz.timezone('*')
    bj_dt = bj_tz.localize(t)
    return bj_dt.astimezone(pytz.UTC)

使用这个可以正常工作……

3

这是一个关于Celery 4的错误,我在Celery 4.2中帮助修复了这个问题。确实,之前的解决办法是把项目的时区设置为UTC,但从Celery 4.2开始,你可以再次使用任何你想要的时区,并且celerybeat的调度会正常工作。更多细节可以查看原来的错误报告和合并请求:https://github.com/celery/celery/pull/4324

14

你可以把你的 CELERY_TIMEZONE 设置为 'UTC',这样会更简单。如果你想用本地时间来安排事件,可以这样做:

london_tz = pytz.timezone('Europe/London')
london_dt = london_tz.localize(datetime.datetime(year, month, day, hour, min))
give_this_to_celery = london_dt.astimezone(pytz.UTC)

确实,这样做会多一些步骤。你需要先把一个日期时间转换为本地时间,然后再转换回来,得到一个不带时区的日期时间。但这样可以解决大部分与时区相关的麻烦。

补充说明:

你能告诉我 CELERY_TIMEZONE 到底是干什么的吗?还有,celery是如何利用 eta 值来计算倒计时的?

Celery 允许你通过指定 eta 参数来延迟执行一个函数。eta 是一个 datetime 对象,表示你希望函数运行的时间。CELERY_TIMEZONE 则指定了用于 eta 的日期时间的时区。所以,如果我们设置 CELERY_TIMEZONE = 'America/New_York',那么所有的 eta 参数都会被理解为纽约时间。

更好的做法是把 CELERY_TIMEZONE 设置为 'UTC',并传递表示 UTC 时间戳的日期时间对象。这样可以避免很多由于夏令时引起的问题。

更多信息可以在 文档中找到

补充:

请查看 asksol 在评论中对 eta 参数构造的澄清和更正。

撰写回答