【PostgreSQL教程】· SysLogger日志收集器工作原理


更多"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数据库服务工作的日志排查有着辅助性的帮助。下一节将继续对其他辅助进程的工作原理进行分析。

全部评论

相关推荐

牛客316659795号:不是,证明hr初筛已经过了,要投给部门筛一遍
点赞 评论 收藏
分享
群星之怒:1.照片可以换更好一点的,可以适量P图,带一些发型,遮住额头,最好穿的正式一点,可以适当P图。2.内容太少。建议添加的:求职意向(随着投递岗位动态更改);项目经历(内容太少了建议添加一些说明,技术栈:用到了什么技术,还有你是怎么实现的,比如如何确保数据传输稳定的,角色注册用到了什么技术等等。)项目经历是大头,没有实习是硬伤,如果项目经理不突出的话基本很难过简历筛。3.有些内容不必要,比如自我评价,校内实践。如果实践和工作无关千万别写,不如多丰富丰富项目。4.排版建议:建议排版是先基础信息,然后教育背景(要突出和工作相关的课程),然后专业技能(一定要简短,不要长篇大论,写你会什么,会的程度就可以),然后是项目经历(一定要详细,占整个简历一定要超过一半,甚至超过百分之70都可以)。最后如果有一部分空白的话可以填补上校内获得的专业相关的奖项,没有就写点校园经历和自我评价。5.技术一定要够硬,禁得住拷打。还有作息尽量保证正常,不要太焦虑。我24双非本科还是非科班,秋招春招各找了一段实习结果都没有转正,当时都想噶了,最后6月份在校的尾巴也找到一份工作干到现在,找工作有时很看运气的不要急着自我否定。 加油
点赞 评论 收藏
分享
评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客企业服务