PostgreSQL可以被扩展来在独立进程中运行用户提供的代码。这种进程被postgres启动、停止和监控,这使它们的生命期与服务器的状态紧密联系。这些进程具有选项可以挂接上PostgreSQL的共享内存区域,并且可以从内部连接到数据库。它们也可以连续地运行多个事务,就像一个正常的被客户端连接的服务器进程。同样,通过链接到libpq,它们可以连接到服务器并像一个正常客户端应用工作。
警告 |
在使用后台工作者进程时具有相当大的鲁棒性和安全性风险,因为它们由C语言编写,对数据具有无限制的访问权。希望使用包括后台工作者进程在内的模块的管理员必须要极度小心。只有仔细审计过的模块才会被允许运行后台工作者进程。 |
通过将模块名放在shared_preload_libraries中,可以在PostgreSQL被启动时初始化后台工作者。一个希望运行后台工作者的模块需要通过在其_PG_init()
中调用RegisterBackgroundWorker(BackgroundWorker *worker)
来注册它。也可以在系统启动后通过调用函数RegisterDynamicBackgroundWorker(BackgroundWorker
*worker, BackgroundWorkerHandle **handle)
来启动后台工作者。与只能在postmaster内调用的RegisterBackgroundWorker
不同,必须从一个常规后端调用RegisterDynamicBackgroundWorker
。
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 */ bgworker_main_type bgw_main; char bgw_library_name[BGW_MAXLEN]; /* only if bgw_main is NULL */ char bgw_function_name[BGW_MAXLEN]; /* only if bgw_main is NULL */ Datum bgw_main_arg; char bgw_extra[BGW_EXTRALEN]; int bgw_notify_pid; } BackgroundWorker;
bgw_name是一个用于记录消息、处理列表和类似上下文的字符串。
bgw_flags是一个按位与的位掩码,它用于指示模块想要的能力。可能的值是BGWORKER_SHMEM_ACCESS(请求共享内存访问)和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启动进程之前等待的时间间隔,以秒计。它可以是任何正值,或者BGW_NEVER_RESTART,表示在出现崩溃后不重启进程。
bgw_main是进程被启动后要运行的函数指针。该函数必须有一个类型为Datum的单一参数并返回void。bgw_main_arg将被传递给它作为它唯一的参数。注意全局变量MyBgworkerEntry指向在注册阶段被传递的BackgroundWorker结构副本。 bgw_main可以为NULL,在那时, bgw_library_name和 bgw_function_name将被用来确定入口点。这对于在 postmaster启动后开始的后台工作者很有用,因为这种情况下postmaster并没有载入必需的库。
bgw_library_name是应该在其中定位后台工作者初始入口点的 库名称。除非bgw_main为NULL,它将会被忽略。但是如果 bgw_main为NULL,则所指的库将被工作者进程动态载入并且 bgw_function_name将被用来标识要调用的函数。
bgw_function_name是一个动态载入库中的一个函数名, 该函数将被用作一个新后台工作者的初始入口点。除非bgw_main 为NULL,它将会被忽略。
bgw_extra包含传递到后台工作者的额外数据。不像bgw_main_arg, 这个数据不会作为参数传递给工作者的主函数,但是它可以通过MyBgworkerEntry被访问,正如上面讨论的。
bgw_notify_pid是一个PostgreSQL后端进程的PID, 当后台工作者进程启动或者退出时,postmaster会向这个PID所指的进程发送SIGUSR1。 对于在postmaster启动时注册的工作者,它应该为0;或者注册该工作者的后端不希望等待该工作者 启动时,它也应该为0。否则,它应该被初始化为MyProcPid。
一旦运行起来,
进程可以通过调用BackgroundWorkerInitializeConnection(char *dbname, char *username)
或者BackgroundWorkerInitializeConnectionByOid(Oid dboid, Oid useroid)
来连接到一个数据库。
这使得该进程可以使用SPI接口运行事务和查询。
如果dbname为NULL或者dboid是InvalidOid,
该会话没有连接到任何特定数据库,但共享的目录可以被访问。
如果username为NULL或者useroid是InvalidOid,
该进程将以在initdb阶段创建的超级用户身份运行。
后台工作者只能调用这两个函数之一,并且只能调用一次。所以不可能切换数据库。
当控制到达bgw_main函数时,信号初始会被阻塞,并且必须被它解除阻塞。这是为了允许进程自定义它的信号处理器。在新进程中可以通过调用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_STOPPED或者
BGWH_POSTMASTER_DIED。
src/test/modules/worker_spi模块包含一个工作例子,它证明了一些有用的技术。
注册的后台工作者的最大数量由max_worker_processes限制。