后台作业

Frappe 内置了一套在后台运行任务的系统。它通过使用 schedule 包和一个简单的长时间运行的无限 while 循环来实现。

你可以使用 frappe.enqueue 方法将一个 Python 方法加入队列以在后台运行:

def long_running_job(param1, param2):
    # expensive tasks
    pass

# directly pass the function
frappe.enqueue(long_running_job, queue='short', param1='A', param2='B')

# or pass the full module path as string
frappe.enqueue('app.module.folder.long_running_job', queue='short', param1='A', param2='B')

以下是你传递给 enqueue 的所有可能参数:

frappe.enqueue(
    method, # python function or a module path as string
    queue="default", # one of short, default, long
    timeout=None, # pass timeout manually
    is_async=True, # if this is True, method is run in worker
    now=False, # if this is True, method is run directly (not in a worker) 
    job_name=None, # specify a job name
    enqueue_after_commit=False, # enqueue the job after the database commit is done at the end of the request
    at_front=False, # put the job at the front of the queue
    **kwargs, # kwargs are passed to the method as arguments
)

你也可以使用 frappe.enqueue_doc 将一个文档(Document)方法加入队列:

frappe.enqueue_doc(
    doctype,
    name,
    "do_something", # name of the controller method
    queue="long",
    timeout=4000,
    param="value"
)

队列

框架默认配置了 3 个队列:shortdefaultlong。每个队列都有一个默认超时时间,如下所示:

  • short:300 秒
  • default:300 秒
  • long:1500 秒

你也可以向 enqueue 方法传递自定义的超时时间。

自定义队列

你可以通过在 [common_site_config.json](https://frappeframework.com/docs/v14/user/en/basics/site_config#common-site-config) 中进行配置来添加自定义队列:

{
    ...
    "workers": {
        "myqueue": {
            "timeout": 5000, # queue timeout
            "background_workers": 4, # number of workers for this queue
        }   
    }
}

工作进程

默认情况下,Frappe 会设置 3 种工作进程类型来消费各个队列中的任务。默认配置如下所示:

bench worker --queue short
bench worker --queue default
bench worker --queue long

在生产环境中,这 3 个工作进程会被复制到配置数量的后台工作进程中,以处理更高的工作负载。

注意:这种将工作进程映射到单个队列的方式只是一种约定,并非必须遵循。

多队列消费

你可以通过指定一个逗号分隔的队列名称字符串,来让工作进程消费多个队列。

示例:如果你想要合并 short 和 default 工作进程,并且只使用两种类型的工作进程而不是默认配置,你可以像这样修改你的工作进程配置:

bench worker --queue short,default
bench worker --queue long

注意:这里展示的示例是针对 Procfile 格式的,但它们也很容易应用于 supervisor 或 systemd 配置。

使用 --burst 的突发模式

bench worker --queue short --burst

该命令会生成一个临时工作进程,该进程将开始消费 short 队列,并在队列清空后退出。如果你定期需要更多的工作进程,你可以使用操作系统的 crontab 在特定时间设置突发工作进程。

调度器事件

你可以使用调度器事件,通过 scheduler_events 钩子在后台定期运行任务。

app/hooks.py

scheduler_events = {
    "hourly": [
        # will run hourly
        "app.scheduled_tasks.update_database_usage"
    ],
}

app/scheduled_tasks.py

def update_database_usage():
    pass

在 hooks.py 中更改任何计划事件后,你需要运行 bench migrate 才能使更改生效。

可用事件

  • hourlydailyweeklymonthly

这些事件将分别每小时、每天、每周和每月触发一次。

  • hourly_longdaily_longweekly_longmonthly_long

与上述相同,但这些任务在 long 工作进程中运行,适用于长时间运行的任务。

  • all

all 事件每 4 分钟触发一次。这可以通过 common_site_config.json 中的 scheduler_interval 键进行配置。

  • cron

一个有效的 cron 字符串,可以被 croniter 解析。

使用示例:

scheduler_events = {
    "daily": [
        "app.scheduled_tasks.manage_recurring_invoices"
    ],
    "daily_long": [
        "app.scheduled_tasks.take_backups_daily"
    ],
    "cron": {
        "15 18 * * *": [
            "app.scheduled_tasks.delete_all_barcodes_for_users"
        ],
        "*/6 * * * *": [
            "app.scheduled_tasks.collect_error_snapshots"
        ],
        "annual": [
            "app.scheduled_tasks.collect_error_snapshots"
        ]
    }
}

可配置的调度器事件

在需要用户可配置触发间隔的场景下,创建一个 Scheduler Event 记录,并针对该记录创建一个 Scheduled Job Type 条目。这不需要 scheduler_event 钩子。

示例:

# Create `Scheduler Event` record
sch_eve = frappe.new_doc("Scheduler Event")
sch_eve.scheduled_against = "Process Payment Reconciliation"
sch_eve.save()

# Create `Scheduled Job Type`
job = frappe.new_doc("Scheduled Job Type")
job.frequency = "Cron"
job.scheduler_event = sch_eve.name
job.cron_format = "0/5 * * * *"     # runs every five minutes
job.save()

Scheduled Job Type 的触发间隔可以在之后修改,并且会在 bench 迁移时保持不变。

由调度器触发的任务由 管理员 用户运行。这也意味着,除非另有指定,否则你通过计划任务创建的任何文档都将归 管理员 用户所有。