更多"C/C++、PostgreSQL、编译原理、计算机原理、TCP/IP、数据结构&算法、Linux编程”等技术文章更新于公众号: 君子黎
1. SysLogger系统日志收集器
在 【PostgreSQL教程】· PostgreSQL配置管理日志(一) 一文中,详细介绍了如何在PostgreSQL中开启日志收集器,以及配置log文件存储目录和大小,同时还介绍了许多与log文件相关联的配置参数。此外还说明了log文件在PostgreSQL10.0之前与之后的一些细微差异化变动。本节内容主要用于分析SysLogger日志收集器的内部原理,在学习 了本文之后,将对Logger的工作方式有着更加清晰的认识。
1.1 SysLogger启动入口
PostgreSQL是一个客户端/服务器模式(C/S)架构,整个服务的初始化代码入口是main.c(/src/backend/main)文件中的main函数。在main函数中会根据启动参数选项来进行判断,并走不同的分支。然后进行postmaster守护进程初始化操作,这一初始化过程主要在postmaster.c文件中实现(位于/src/backend/postmaster/目录)。守护进程postmaster负责整个系统的启动和关闭,它监听并接受来自客户端的连接请求,并未其每一个请求分配一个postgres服务。之后该客户端连接上面的所有请求操作都直接与postgres进程进行交互,而不再经由postmaster守护进程参与。
SysLogger日志收集器的初始化入口是SysLogger_Start函数(/src/backend/postmaster/syslogger.c)。在初始化日志收集器前,会对GNC全局变量参数Logging_collector进行判断(对应postgresql.conf配置文件中的logging_collector,更多细节阅读 【PostgreSQL教程】· PostgreSQL配置管理日志(一) ),若该参数值为true,则表示开启日志收集器进程logger,反之则退出,不开启logger进程。
1.1.1 SysLogger日志收集器进程名
SysLogger日志收集器的守护进程名是logger,在初始化SysLogger进程的时候,会对全局变量 MyBackendType (BackendType MyBackendType;)进程初始化为:B_LOGGER 的操作(MyBackendType = B_LOGGER;),后面在创建守护进程时候,根据GetBackendTypeDesc()函数获取对应的守护进程名。在PostgreSQL 13.2版本***支持以下几种类型的后台守护进程,如下所示枚举值(更多关于枚举类型的知识请阅读 C/C++ 枚举类型)。
typedef enum BackendType { B_INVALID = 0, B_AUTOVAC_LAUNCHER, B_AUTOVAC_WORKER, B_BACKEND, B_BG_WORKER, B_BG_WRITER, B_CHECKPOINTER, B_STARTUP, B_WAL_RECEIVER, B_WAL_SENDER, B_WAL_WRITER, B_ARCHIVER, B_STATS_COLLECTOR, B_LOGGER, } BackendType;
以下是各守护进程(枚举值)对应的进程名字:
1.1.2 PostgreSQL默认开启的守护进程
并非所有的守护进程都会默认开启,有些是需要在postgresql.conf配置文件中进手动配置启动,比如日志收集器,就需要置参数logging_collector为on。 默认情况下,PostgreSQL仅开启了 checkpointer、background write、walwriter、autovacuum launcher、stats collector、logical replication launcher 这几个后台守护进程,如下图所示:
1.1.3 SysLogger日志收集器启动流程
1.1.3.1 SysLogger系统日志整体初始化流程图
SysLogger日志收集器的整体初始化过程如下流程图所示:
在初始化日志收集器时候,先根据postgres.conf配置文件中的参数(log_directory)来创建对应的log日志目录,默认log日志目录权限为文件拥有者具有读、写和执行权限。如下:
#ifndef S_IRWXU #define S_IRWXU (S_IRUSR | S_IWUSR | S_IXUSR) #endif int MakePGDirectory(const char *directoryName) { return mkdir(directoryName, pg_dir_create_mode); }
目录创建好之后,再获取当前系统时间,然后将时间按照postgresql.conf配置文件中的log_filename参数的值进行对应的格式化,生成一个新log文件。然后以文件访问模式“+a”的方式打开log文件,若不存在则新建,并且对该log文件的属性进行调整,同时设置该log文件的文件流缓冲区为行缓存(_IOLBF)形式。在log文件创建成功之后,并调用函数fork_process()创建logger子进程(fork_process函数是fork函数的封装,包括返回值都匹配fork.),并在子进程中对内存相关的参数(OOM)进行一些内部设置。之后该函数返回进程的PID。
根据PID的值进行对应的其他工作处理,若PID为0,则表示为子进程中,在子进程中会初始化一些与子进程状态相关的全局变量、注册父进程状态信号、关闭读管道、关闭postmaster父进程中的监听套接字和与父进程postmaster相关的内存数据等。然后进入SysLoggerMain()函数真正开始logger日志收集器进程的相关处理操作。 而在父进程中,则会先刷新stout、stderr等文件描述符的缓冲区数据,然后在将stdout、stderr文件描述符重定向到管道syslogPipe[1]d 写端,接着关闭管道写端和日志文件描述符句柄syslogFile。 因为postmaster父进程将永远不会向该log文件中写数据。
之后父进程postmaster中将返回logger日志收集器的子进程PID。 该子进程PID将用于postmaster父进程的ServerLoop()函数中。ServerLoop()函数是守护进程postmaster(父进程)的主要空循环处理函数。该函数为一个死循环函数(for( ; ; )), 该函数内部主要负责对 checkpointer(检查点进程)、background write(后台写进程)、walwriter(预写式日志写进程)、autovacuum launcher(系统自动清理进程)、stats collector(统计数据搜集进程)、logger(系统日志进程)、archiver(预写式日志归档进程)等 辅助守护进程的状态管理维护,若发现其中某个进程PID丢失,则立刻重新创建一个新的对应守护进程。
比如对于下图中的几个辅助进程logger、background writer、walwriter、autovacuum launcher、stats collector、logical replication launcher,若其中一个被手动人为kill掉,则postmaster守护进程将会检查到对应辅助子进程被kill掉的状态和对应信号(若开启了最高等级(debug5)日志,则会打印出对应信号值)。然后立刻重新fork()一个对应的子进程。备注:我结合代码逻辑亲自测试过,实际情况与逻辑是相吻合、匹配的。
此外,ServerLoop()函数还负责监听用户的连接请求,对于用户下发的每个请求,postmaster都会fork一个子进程(postgres)来进行处理,之后的该用户的所有请求操作,包括数据库、表、索引等的增删改查等操作都交由该postgres进程处理响应。因此PostgreSQL是一个多进程的客户端/服务器模型。
if (selres > 0) { int i; for (i = 0; i < MAXLISTEN; i++) { if (ListenSocket[i] == PGINVALID_SOCKET) break; if (FD_ISSET(ListenSocket[i], &rmask)) { Port *port; port = ConnCreate(ListenSocket[i]); if (port) { BackendStartup(port); /* * We no longer need the open socket or port structure * in this process */ StreamClose(port->sock); ConnFree(port); } } } }
在接收到用户的连接请求后,ServerLoop()函数将首先创建一个与该请求对应的本地连接ConnCreate()。之后的fork子进程等工作则交给BackendStartup()函数中去处理。当fork进程成功后,父进程中将会把本次创建的子进程的PID放入到后端活动的进程PID链表中,该工作由dlist_push_head()函数负责完成。
3. 总结
到这里为止,较为详细地对PostgreSQL 8.0引入的logger系统日志收集器的初始化流程与工作原理做了梳理和总结,通过本文的阅读学习,将提升你对logger辅助进程的理解。同时,这也对PostgreSQL数据库服务工作的日志排查有着辅助性的帮助。下一节将继续对其他辅助进程的工作原理进行分析。