章 47.后台工作进程

PostgreSQL 可以扩展为在单独的进程中运行用户提供的代码。此类进程由postgres启动,停止和监视,从而使它们的生存期与服务器状态紧密相关。这些进程可以选择附加到 PostgreSQL 的共享内存区域并在内部连接到数据库。它们还可以串行运行多个事务,就像常规的 Client 端连接服务器进程一样。而且,通过链接到 libpq,他们可以连接到服务器,并且表现得像普通的 Client 端应用程序。

Warning

使用后台工作进程具有很大的健壮性和安全性风险,因为使用C语言编写的后台工作进程可以不受限制地访问数据。希望启用包含后台工作进程的模块的 Management 员应格外小心。仅允许经过仔细审核的模块运行后台工作进程。

通过在shared_preload_libraries中包含模块名称,可以在启动 PostgreSQL 时初始化后台工作程序。希望运行后台工作程序的模块可以通过从_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];
    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];
    int         bgw_notify_pid;
} BackgroundWorker;

bgw_name是在日志消息,进程列表和类似上下文中使用的字符串。

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

  • BGWORKER_SHMEM_ACCESS

    • 请求共享内存访问。没有共享内存访问权限的工作程序无法访问 PostgreSQL 的任何共享数据结构,例如重量级或轻量级锁,共享缓冲区或工作程序本身可能希望创建和使用的任何自定义数据结构。
  • BGWORKER_BACKEND_DATABASE_CONNECTION

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

bgw_start_time是服务器状态,在此期间postgres应开始该过程;它可以是BgWorkerStart_PostmasterStart中的一种(postgres本身完成其自身的初始化后立即开始;请求此初始化的进程不符合数据库连接的条件),BgWorkerStart_ConsistentState(在热备用数据库中达到一致状态后立即启动,从而允许进程连接到数据库并运行只读查询)和BgWorkerStart_RecoveryFinished(在系统进入正常读写状态后立即启动)。请注意,在不是热备用服务器的服务器中,最后两个值是等效的。请注意,此设置仅指示何时开始进程。当达到其他状态时,它们不会停止。

bgw_restart_time是间隔postgres应该 await 的时间间隔(以秒为单位),以防崩溃。它可以是任何正值或BGW_NEVER_RESTART,表示在发生崩溃时不重新启动该过程。

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

bgw_function_name是动态加载的库中的函数名称,应将其用作新后台工作程序的初始入口点。

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

在 Windows(以及定义EXEC_BACKEND的其他任何位置)上或在动态后台工作程序中,仅通过值传递Datum并不安全。如果需要一个参数,则最安全的方法是传递一个 int32 或其他较小的值,并将其用作索引到共享内存中分配的数组中。如果传递了cstringtext之类的值,则该指针在新的后台工作进程中将无效。

bgw_extra可以包含要传递给后台工作人员的其他数据。与bgw_main_arg不同,此数据不会作为参数传递给工作程序的 main 函数,但可以通过MyBgworkerEntry进行访问,如上所述。

bgw_notify_pid是 PostgreSQL 后端进程的 PID,在启动或退出该进程时,邮局主管应将SIGUSR1发送给该进程。对于在邮局主管启动时注册的工作人员,或在注册该工作人员的后端不希望 await 该工作人员启动时注册的工作人员,它应该为 0.否则,应将其初始化为MyProcPid

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

当控制权到达后台工作者的主要功能时,signal 最初会被阻塞,并且必须被其阻塞。这是为了允许该进程自定义其 signal 处理程序(如有必要)。可以通过调用BackgroundWorkerUnblockSignals来取消阻塞 signal,并通过调用BackgroundWorkerBlockSignals来阻塞 signal。

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

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

在某些情况下,注册后台工作人员的进程可能希望 await 该工作人员启动。这可以通过将bgw_notify_pid初始化为MyProcPid,然后将在注册时获得的BackgroundWorkerHandle *传递给WaitForBackgroundWorkerStartup(BackgroundWorkerHandle *handle, pid_t *)函数来实现。该功能将一直阻塞,直到邮局主管尝试启动后台工作人员或邮局主管去世为止。如果后台运行程序正在运行,则返回值将为BGWH_STARTED,并且 PID 将被写入提供的地址。否则,返回值为BGWH_STOPPEDBGWH_POSTMASTER_DIED

如果后台工作人员通过服务器编程接口(SPI)使用NOTIFY命令发送异步通知,则它应在提交封闭事务后显式调用ProcessCompletedNotifies,以便可以传递任何通知。如果后台工作程序注册为通过 SPI 通过LISTEN接收异步通知,则工作程序将记录这些通知,但是工作程序无法通过编程方式来拦截和响应这些通知。

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

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