文章转载自公众号:君子黎
作者:圣手书生
1. 关于文件描述符
1.1 什么是文件描述符
文件描述符(file descriptor)是唯一标识计算机操作系统中打开文件的数字(非负整数),它描述了数据资源,以及如何访问该资源。当进程需要打开一个文件(网络套接字、字符设备等)时,内核授予访问权限,并在其全局文件表中创建一个条目,然后向进程提供该条目的位置。
也就是说,类UNIX内核使用fd引用所有打开的文件。对于文件描述符,它具有以下两个属性:
每个进程都有自己的增量fd空间。被关闭的fd占用的正整数可以被重复利用。单个进程可以同时打开的文件句柄数量受到系统limit设置的限制的数量。
按照约定,所以shell在启动一个新应用程序时候,总是打开了这三个文件描述符,它们分别是:0(标准输入)、1(标准输出)、2(标准错误)。
由于操作系统内核对每个进程同时刻所能打开的文件句柄数量作了限制,因此PostgreSQL数据库在进程起来的时,会对可打开的文件句柄数量作一个评估,然后初始化一个可打开文件句柄数量的安全值。
2. PostgreSQL评估可打开的fd数量
在初始化postmaster守护进程时,当设置了信号量之后,便开始统计、评估当前系统环境上可打开文件句柄的数量。之所以要在初始化信号量之后,是因为在某些平台上,信号量会被视为打开的文件。
该评估过程主要由函数 set_max_safe_fds() 完成。具体统计过程如下:
2.1 获取当前系统允许打开的文件句柄个数,以及有多少文件句柄已经打开(被使用)
这里主要使用了类UNIX的系统函数getrlimit(),该函数的功能是获取操作系统的资源限制。操作系统内核默认会对系统资源做一些默认值的限制,比如允许打开的文件句柄个数、最大用户进程数、文件大小等等,如下图所示:
getrlimit()函数的格式声明为:

该函数接收两个参数,参数resource指定想要获取对应类型的资源限制,比如:RLIMIT_NOFILE(每个进程可以打开的最大文件数)、RLIMIT_AS(进程可用存储区的最大总长度-字节)等等。struct rlimit是一个结构体类型声明,该类型包括两个结构体成员,如下:
struct rlimit { rlim_t rlim_cur; /* Soft limit */ rlim_t rlim_max; /* Hard limit (ceiling for rlim_cur) */ };
其中rlim_cur表示软限制;rlim_max表示硬件限制。软限制是内核对相应资源强制执行的值,而硬限制充当软限制的上限:非特权进程只能将软限制设置为从0到硬限制的范围的值,并(不可逆地)降低其硬限制。特权进程(在Linux下:具有CAP_SYS_RESOURCE功能的进程)可以对任意一个限制值进行更改。值RLIM_INFINITY表示对资源没有限制(无论是在getrlimit()返回的结构中还是传递给setrlimit()的结构中)。
PostgreSQL数据库中默认每个进程能够打开的最大文件描述符数量是:1000。该值的定义是在fd.c文件中(fd.c文件位于src/backend/storage/file/fd.c)。
int max_files_per_process = 1000;
如果getrlimit()函数执行成功,那么一直调用dup(0)系统函数,直到循环次数大于等于max_files_per_process,或者是dup(0)返回的最小可用文件描述符大于等于rlim.rlim_cur(资源软限制,默认是1024)时,则结束循环。
当跳出循环之后,一 一释放fd指针变量中打开的文件句柄。默认申请了1024个文件句柄空间,并且max_files_per_process最大值是1000,所以后面的24个索引是0,无文件句柄。
int *fd = NULL;size = 1024;fd = (int *) palloc(size * sizeof(int));
最终释放fd指针变量申请的内存空间,并且统计出当前系统可用的文件句柄个数(usable_fds,这个句柄数量并不是真正的可打开的文件句柄数,它仅仅代表dup成功的数量),并且计算当前系统已经打开的文件句柄数(默认0、1、2是已经打开),其计算方法是:当前最小的可用文件描述符 + 1 - 成功打开的文件句柄数。
*usable_fds = used;*already_open = highestfd + 1 - used;
如下图所示,可以计算得知当前已经打开了3个文件句柄。(记住:0是一个合法的fd数)
对于统计系统资源的以使用句柄数,其完整的流程图如下:
2.2 计算PostgreSQL可以安全使用的文件句柄个数
在2.1中得出准确的已打开文件句柄数,尽管也返回了可使用的文件句柄个数(usable_fds),但是这个数是有些风险的,因为它只是默认的1000。为了降低风险以及减去不用咨询fd.c(fd.c实现了VFD机制,对外提供虚拟的经过封装了的FD,后面会专门出一篇博文讲解PostgreSQL的VFD)就打开的倾斜系数。PostgreSQL采取的保证系统可安全使用的最大fd计算方式是:
取 max_safe_fds = Min(usable_fds, max_files_per_process - already_open)的最小值。
#define Min(x, y) ((x) < (y) ? (x) : (y))
其中usable_fds等于max_files_per_process(1000),already_open(3)。所以可用的是 997个。
此外,fd.c文件中特意说明了,我们还必须为system()、动态加载程序和其他试图在不咨询fd.c的情况下打开文件的代码保留一些文件描述符 。这里保留的文件描述符个数是:10个。
#define NUM_RESERVED_FDS 10
最终系统可安全使用的最大文件描述符个数是:
max_safe_fds -= NUM_RESERVED_FDS。
max_safe_fds = Min(usable_fds, max_files_per_process - already_open);/* * Take off the FDs reserved for system() etc. */max_safe_fds -= NUM_RESERVED_FDS;
2.3 当PostgreSQL可使用的fd数量小于48,则进程启动失败
如果最终PostgreSQL可使用的文件描述符数量小于48个,那么将不允许postmaster服务起来。(选择FD_MINFREE(64)这个值是为了使用" ulimit -n 64", 但也不能比这个小太多。注意,这个值确保numExternalFDs可以至少为16;PostgreSQL作者在编写这些代码是,contrib/postgres_fdw回溯测试将不能通过,除非它能增长到至少14个)
#define FD_MINFREE 48/* * Make sure we still have enough to get by. */if (max_safe_fds < FD_MINFREE) ereport(FATAL, (errcode(ERRCODE_INSUFFICIENT_RESOURCES), errmsg("insufficient file descriptors available to start server process"), errdetail("System allows %d, we need at least %d.", max_safe_fds + NUM_RESERVED_FDS, FD_MINFREE + NUM_RESERVED_FDS)));
若可安全使用的最大文件描述符个数大于48,那么初始化全局变量 max_safe_fds 。这个变量非常重要,本文前面提到过,PostgreSQL内部使用了VFD机制,对外提供的是虚拟的文件fd,内部对真实的文件句柄fd进行了封装,每次底层去获取fd时候,都会使用这个全局变量 max_safe_fds来进行判断。以达到安全使用fd以及确保进程不会因为超出系统的文件描述符个数限制而产生一些内核级别的bug。
2.4 成功获可安全使用的最大文件描述符数量
成功初始化max_safe_fds变量之后,便日志打印告知当前的相关变量值信息。
elog(DEBUG2, "max_safe_fds = %d, usable_fds = %d, already_open = %d", max_safe_fds, usable_fds, already_open);
新闻|Babelfish使PostgreSQL直接兼容SQL Server应用程序
更多新闻资讯,行业动态,技术热点,请关注中国PostgreSQL分会官方网站
https://www.postgresqlchina.com
中国PostgreSQL分会生态产品
https://www.pgfans.cn
中国PostgreSQL分会资源下载站
https://www.postgreshub.cn
点击此处阅读原文
↓↓↓