在前面的博文中提到,可以利用 PostgreSQL 的 Hook 技术,来定制化数据库行为。其原理就是利用全局的函数指针,来判断PG中预制的各种HOOK是否为空, 如果不为空,则指向用户自定义的HOOK函数(注册用户的自定义 hook)。现在我们来具体看一下 postgres 中 hook 的实现机制。
- PG 通过全局指针函数(global pointer),来判断PG中预制的各种HOOK是否为空, 如果不为空,则指向用户自定义的HOOK函数(注册用户的自定义 hook)。
- 如果参数shared_preload_library 中包含多个插件,并且存在多个插件都修改同一个HOOK的情况,那么会把previous hook 保存记录下来, 如果 previous hook 不为空,会在自己开发中的HOOK中调用previous hook, 从而避免了后面的HOOK覆盖掉之前HOOK的逻辑,类似于 HOOK 链的设计。
- 当PG启动的时候,会调用方法__PG__init()来加载shared library 的 *.so 文件:
- 当PG关系实例的时候, 会调用方法__PG___finit()来关闭掉shared library 中加载的插件。
PG 预制HOOK的种类:
- General Hooks
- Security Hooks
- Function Manager Hooks
- Planner Hooks
- Executor Hooks
- PL/pgsql Hooks
- ...
例如常用的pg_stat_statements和auto_explain,安装插件时,第一步就需要先修改 postgres.conf 中的参数 shared_preload_libraries,启动的时候加载2个插件:pg_stat_statements 和 auto_explain:
shared_preload_libraries='pg_stat_statements,auto_explain'
在 src/backend/postmaster/postmaster.c 中,主流程 PostmasterMain 中,调用 process_shared_preload_libraries()
可以看到 libraries 是值 是我们的GUC 的shared_preload_libraries值:是一个字符串形式,这个字符串就是我们配置文件中的那一串。
load_libraries 会把字符串按照逗号拆分放入 elemlist, 进行循环加载调用函数 load_file。
其中 internal_load_library 这个函数是实际的加载链接文件的入口;
调用底层 操作系统OS 函数 dlopen 打开动态链接文件
通过dlsym 调用动态加载文件中的 PG_MODULE_MAGIC 判断插件程序的兼容性。
如果存在不兼容的情况,则会调用dlclose(file_scanner->handle); 卸载动态库。
验证完插件的兼容性之后,会调用插件中的初始化函数 _PG_init(void)
在pg_stat_statements.c 文件中, 初始化函数 _PG_init(void) 完成了:
- EnableQueryId 设置SQL 的query ID
- DefineCustomIntVariable,DefineCustomEnumVariable,DefineCustomBoolVariable 设置用户自己定义的GUC变量
- install hook – 加载钩子函数
可以看到在HOOK的实现上,如果预制的HOOK已经被加载,则是 优先执行之前的PREVIOUS hook。
这样才能保证插件彼此时间不存在互相覆盖的现象。
以上就是 Postgres hook 在内核的加载过程。