Skip to content

第 48 章. 后台工作进程

PostgreSQL 可以扩展为在单独的进程中运行用户提供的代码。此类进程由postgres启动、停止和监控,这允许它们具有与服务器状态紧密相关的生命周期。这些进程附加到PostgreSQL的共享内存区域,并可以选择在内部连接到数据库;它们还可以像常规客户端连接的服务器进程一样串行运行多个事务。此外,通过链接到libpq,它们可以连接到服务器并像常规客户端应用程序一样运行。

警告

使用后台工作进程存在相当大的稳健性和安全风险,因为它们使用C语言编写,可以不受限制地访问数据。希望启用包含后台工作进程的模块的管理员应格外小心。只有经过仔细审核的模块才应被允许运行后台工作进程。

可以在启动PostgreSQL时初始化后台工作进程,方法是在shared_preload_libraries中包含模块名称。希望运行后台工作进程的模块可以通过从其_PG_init()函数调用RegisterBackgroundWorker(BackgroundWorker**worker*)来注册该工作进程。也可以在系统启动并运行后通过调用RegisterDynamicBackgroundWorker(BackgroundWorker**worker*,BackgroundWorkerHandle***handle*)来启动后台工作进程。与只能在后端进程中调用的RegisterBackgroundWorker不同,RegisterDynamicBackgroundWorker必须在常规后端或其他后台工作进程中调用。

结构BackgroundWorker如此定义

typedef void (*bgworker_main_type)(Datum main_arg);
typedef struct BackgroundWorker
{
    char        bgw_name[BGW_MAXLEN];
    char        bgw_type[BGW_MAXLEN];
    int         bgw_flags;
    BgWorkerStartTime bgw_start_time;
    int         bgw_restart_time;       /* in seconds, or BGW_NEVER_RESTART */
    char        bgw_library_name[BGW_MAXLEN];
    char        bgw_function_name[BGW_MAXLEN];
    Datum       bgw_main_arg;
    char        bgw_extra[BGW_EXTRALEN];
    pid_t       bgw_notify_pid;
} BackgroundWorker;

bgw_namebgw_type是用于日志消息、进程列表和类似上下文的字符串。对于同一类型的后台工作进程,bgw_type应相同,以便在进程列表中对这些工作进程进行分组。另一方面,bgw_name可以包含有关特定进程的其他信息。(通常,bgw_name的字符串将以某种方式包含类型,但这不是严格要求的。)

bgw_flags是按位或运算的位掩码,指示模块所需的功能。可能的值为

BGWORKER_SHMEM_ACCESS

请求共享内存访问。此标志是必需的。

BGWORKER_BACKEND_DATABASE_CONNECTION

请求建立数据库连接的能力,以后可以通过该连接运行事务和查询。使用 BGWORKER_BACKEND_DATABASE_CONNECTION 连接到数据库的后台工作进程还必须使用 BGWORKER_SHMEM_ACCESS 附加共享内存,否则工作进程启动将失败。

bgw_start_timepostgres应在该状态下启动进程的服务器状态;它可以是BgWorkerStart_PostmasterStart(在postgres本身完成自己的初始化后立即启动;请求此项的进程没有资格进行数据库连接)、BgWorkerStart_ConsistentState(在热备用中达到一致状态后立即启动,允许进程连接到数据库并运行只读查询)和BgWorkerStart_RecoveryFinished(在系统进入正常读写状态后立即启动)。请注意,后两个值在不是热备用的服务器中是等效的。请注意,此设置仅指示进程何时启动;当达到不同的状态时,它们不会停止。

bgw_restart_timepostgres在进程崩溃时应等待重新启动进程的时间间隔(以秒为单位)。它可以是任何正值,也可以是BGW_NEVER_RESTART,表示在崩溃时不重新启动进程。

bgw_library_name是应在其中查找后台工作程序的初始入口点的库的名称。命名库将由工作程序进程动态加载,bgw_function_name将用于标识要调用的函数。如果在核心代码中调用函数,则必须将其设置为"postgres"

bgw_function_name是用作新后台工作程序的初始入口点的函数的名称。如果此函数位于动态加载的库中,则必须将其标记为PGDLLEXPORT(而不是static)。

bgw_main_arg是后台工作程序主函数的Datum参数。此主函数应采用类型为Datum的单个参数,并返回voidbgw_main_arg将作为参数传递。此外,全局变量MyBgworkerEntry指向注册时传递的BackgroundWorker结构的副本;工作程序可能会发现检查此结构很有帮助。

在 Windows(以及定义了EXEC_BACKEND的任何其他地方)或动态后台工作程序中,按引用传递Datum不安全,只能按值传递。如果需要参数,最安全的方法是传递 int32 或其他小值,并将其用作共享内存中分配的数组的索引。如果传递cstringtext等值,那么指针将从新的后台工作程序进程无效。

bgw_extra可以包含要传递给后台工作程序的额外数据。与bgw_main_arg不同,此数据不会作为参数传递给工作程序的主函数,但可以通过MyBgworkerEntry访问,如上所述。

bgw_notify_pid是 PostgreSQL 后端进程的 PID,当进程启动或退出时,邮政局长应向其发送SIGUSR1。对于在邮政局长启动时注册的工作程序,或者当注册工作程序的后端不希望等待工作程序启动时,它应为 0。否则,它应初始化为MyProcPid

一旦运行,该进程可以通过调用BackgroundWorkerInitializeConnection(*char *dbname*,*char *username*,*uint32 flags*)BackgroundWorkerInitializeConnectionByOid(*Oid dboid*,*Oid useroid*,*uint32 flags*)连接到数据库。这允许进程使用SPI接口运行事务和查询。如果dbname为 NULL 或dboidInvalidOid,则会话未连接到任何特定数据库,但可以访问共享目录。如果username为 NULL 或useroidInvalidOid,则进程将以initdb期间创建的超级用户身份运行。如果将BGWORKER_BYPASS_ALLOWCONN指定为flags,则可以绕过连接到不允许用户连接的数据库的限制。后台工作程序只能调用这两个函数中的一个,且只能调用一次。无法切换数据库。

当控制权到达后台工作进程的主函数时,信号最初会被阻塞,并且必须由它解除阻塞;这是为了允许进程自定义其信号处理程序(如果需要)。可以通过调用BackgroundWorkerUnblockSignals在新进程中解除信号阻塞,并通过调用BackgroundWorkerBlockSignals阻塞信号。

如果后台工作进程的bgw_restart_time配置为BGW_NEVER_RESTART,或者它以退出代码 0 退出或被TerminateBackgroundWorker终止,它将在退出时由 Postmaster 自动注销。否则,它将在通过bgw_restart_time配置的时间段后重新启动,或者如果 Postmaster 由于后端故障而重新初始化集群,则立即重新启动。需要暂时挂起执行的后端应使用可中断的睡眠,而不是退出;这可以通过调用WaitLatch()来实现。确保在调用该函数时设置WL_POSTMASTER_DEATH标志,并在紧急情况下(postgres本身已终止)验证返回代码以快速退出。

当使用RegisterDynamicBackgroundWorker函数注册后台工作进程时,执行注册的后端可以获取有关工作进程状态的信息。希望执行此操作的后端应将BackgroundWorkerHandle *的地址作为第二个参数传递给RegisterDynamicBackgroundWorker。如果工作进程成功注册,此指针将使用不透明句柄进行初始化,该句柄随后可以传递给GetBackgroundWorkerPid(*BackgroundWorkerHandle **,*pid_t **)TerminateBackgroundWorker(*BackgroundWorkerHandle **)GetBackgroundWorkerPid可用于轮询工作进程的状态:返回值BGWH_NOT_YET_STARTED表示工作进程尚未由 Postmaster 启动;BGWH_STOPPED表示它已启动但不再运行;BGWH_STARTED表示它当前正在运行。在最后一种情况下,PID 也将通过第二个参数返回。TerminateBackgroundWorker导致 Postmaster 向正在运行的工作进程发送SIGTERM,并在工作进程不再运行时立即注销它。

在某些情况下,注册后台工作进程的进程可能希望等待工作进程启动。这可以通过将bgw_notify_pid初始化为MyProcPid,然后将注册时获得的BackgroundWorkerHandle *传递给WaitForBackgroundWorkerStartup(*BackgroundWorkerHandle *handle*,*pid_t **)函数来实现。此函数将阻塞,直到 Postmaster 尝试启动后台工作进程,或者直到 Postmaster 死亡。如果后台工作进程正在运行,返回值将为BGWH_STARTED,并且 PID 将写入提供的地址。否则,返回值将为BGWH_STOPPEDBGWH_POSTMASTER_DIED

一个进程还可以通过使用WaitForBackgroundWorkerShutdown(*BackgroundWorkerHandle *handle*)函数并传递在注册时获得的BackgroundWorkerHandle *来等待后台工作者关闭。此函数将阻塞,直到后台工作者退出或 Postmaster 进程死亡。当后台工作者退出时,返回值为BGWH_STOPPED,如果 Postmaster 进程死亡,则返回值为BGWH_POSTMASTER_DIED

后台工作者可以通过SPI使用NOTIFY命令或直接通过Async_Notify()发送异步通知消息。此类通知将在事务提交时发送。后台工作者不应使用LISTEN命令注册以接收异步通知,因为没有基础设施供工作者使用此类通知。

src/test/modules/worker_spi模块包含一个工作示例,演示了一些有用的技术。

已注册后台工作者的最大数量受max_worker_processes限制。