Author: 佑熙
pg_cron
是 PostgreSQL
(9.5或更高版本)的一个简单的基于cron
的作业调度程序,它作为扩展在数据库中运行。 它与常规 cron
保持相同的语法,但它允许直接从数据库安排 PostgreSQL
命令。作为一个独立运行的工作者进程,其生命周期管理、内存空间都依赖于 postgreSQL
。本文主要从启动、生命周期、状态机、用法介绍该插件在 postgresQL
数据库中的应用。
postgresql
后台的主进程是 postmaster
, 在其启动时,会调用一个函数process_shared_preload_libraries();
这个函数被 postgresql
用来预加载外部插件。该函数会遍历参数列表,然后对列表参数中的插件依次进行加载。对于插件来说加载的过程包括了除了检查环境 和 注册主函数,主函数由插件中的 _PG_init()
完成注册,这个函数从外部环境中 加载进来,被postmaster
执行(类似 Python
的反射)。
PG_init = (PG_init_t) pg_dlsym(file_scanner->handle, "_PG_init");
if (PG_init)
(*PG_init) ();
对于 pg_cron
插件来说,插件的 _PG_init
函数对于主函数进行了注册:将 PgCronWorkerMain
配置为一个后台 worker
并且注册到列表中。到这里系统回到了 postmaster 进程中继续执行任务,直到执行到 maybe_start_bgworkers()
函数,尝试将 workerlist
列表中的worker
启动。(这个执行的过程还与数据库的模式有关,处于 standby mode
状态下的数据库不会去启动 pg_cron
) postmaster 会分配一个 background work
给pg_cron
, 之后pg_cron
进程独立运行。
pg_cron
生命周期pg_cron
插件的主体是围绕 PG_CRON_TASK
进行,从内部来说, PG_CRON_TASK
有自己的生命周期,其生命周期的轮转过程就是插件的运行过程,从外部来说 PG_CRON_TASK
与 PG_CRON_JOB
通信取得当前的任务列表,在运行状态与 POSTMASTER
通信 完成定时任务的运行。
图 1 pg_cron_task
示意图
pg_cron
生命周期中涉及到的主要涉及环境信息 如 图2 所示,主要分为三类:即状态检查信息、标志位返回信息 和 错误返回信息 。而pg_cron
的生命周期的状态转移就由这些信息控制。状态转移状态一般来说也分为三类:状态等待中,正常执行进入下一个状态,错误信息返回。进程从 CRON_TASK_WAITING
开始,依次进入每一个对应的状态,最后流转到 CRON_TASK_DONE
和 CRON_TASK_ERROR
中的一个。 最后这些状态信息被重置,pg_cron
进入下一个生命周期。
图 2 pg_cron_task
生命周期
pg_cron
通信状态机pg_cron
是单进程单线程运行插件,由 postmaster
进程启动, 属于其子进程。在执行多任务的进程中,采用了 生命周期流转 和 多路 IO复用来 模拟了多任务的并行处理,下面介绍该机制。
pg_cron
模拟了一个生命周期队列(如图2所示)来维护所有的任务状态,每个任务都由以下状态组成:等待、开始、连接、发送、运行、接收、完成、错误。其中,大部分状态被认为不会IO阻塞(例如等待状态、开始状态),但是有一些进程可能会由于 socket
阻塞 (例如 连接、发送、运行 )。pg_cron
使用了poll
函数 完成 IO复用。该过程如图3 所示,由于可能的网络时延、任务执行时间不确定,这些任务的并发状况是未知的,而poll
函数便是遍历文件句柄, 接收到 IO 数据后会更新文件句柄,唤醒进程进行 IO ,从而避免了 IO 阻塞。当IO结束后 任务接受一个标志位 任务完成或 任务失败,结束本次生命周期。
图3 pg_cron_task
多路IO复用模型
pg_cron
用法手册每一个定时任务分为两部分: 定时计划 和 定时任务。定时计划规定了用户 使用 插件的计划(例如:每隔1分钟执行一次该任务),定时任务是用户具体的任务内容(例如:select * from some_table
)
普通用户一共有三个可选函数:增加任务项、删除任务项、查看当前任务项。
-- 周六3:30am (GMT) 删除过期数据
SELECT cron.schedule('30 3 * * 6', $$DELETE FROM events WHERE event_time < now() - interval '1 week'$$);
schedule
----------
42
-- 每天的 10:00am (GMT) 执行磁盘清理
SELECT cron.schedule('0 10 * * *', 'VACUUM');
schedule
----------
43
-- 每分钟执行 指定脚本
SELECT cron.schedule('* * * * *', 'select 1;');
schedule
----------
44
-- 每个小时的 23分 执行 指定脚本
SELECT cron.schedule('23 * * * *', 'select 1;');
schedule
----------
45
-- 每个月的 4号 执行 指定脚本
SELECT cron.schedule('* * 4 * *', 'select 1;');
schedule
----------
46
pg_cron
计划使用标准的 cron
语法,其中 * 表示“每个该时间运行”,特定数字表示“仅在 这个数字时 运行”
┌───────────── 分钟 (0 - 59)
│ ┌────────────── 小时 (0 - 23)
│ │ ┌─────────────── 日期 (1 - 31)
│ │ │ ┌──────────────── 月份 (1 - 12)
│ │ │ │ ┌───────────────── 一周中的某一天 (0 - 6) (0 到 6 表示周末到下周六,
│ │ │ │ │ 7 仍然是周末)
│ │ │ │ │
│ │ │ │ │
* * * * *
-- 停止、删除一个任务
SELECT cron.unschedule(42);
unschedule
------------
t
SELECT * FROM cron.job;
jobid | schedule | command | nodename | nodeport | database | username | active
-------+------------+-----------+-----------+----------+----------+----------+--------
43 | 0 10 * * * | VACUUM; | localhost | 5433 | postgres | test | t