“Too many open files”是一个比较常见的错误,不仅仅是在 MySQL 中。只要是在 Linux 中启动的进程,都有可能遇到这个错误。
究其原因,是进程打开的文件描述符数超过了自身的限制。
这个限制,是进程级别的,在 MySQL 中,与 open_files_limit 的设置有关。
但是 open_files_limit 并不是所设即所得,配置的和实际生效的并不完全一样。
配置文件中的配置。
切换到 mysql 用户下,启动实例,看看这三个参数,外加 table_definition_cache 的实际有效值。
实际值和配置值,竟然没有一个相同,是不是很意外?
下面,结合源码看看这三个参数的生成逻辑。
vim mysql-5.7.34/sql/mysqld.cc
其中:
- sys_var_init():初始化系统参数(System Variables)。
- adjust_related_options():在初始化的基础上,调整相关参数的配置。
下面看看 adjust_related_options 的处理逻辑
其中,
- adjust_open_files_limit:调整 open_files_limit,调整后的值会赋值给 requested_open_files。
- adjust_max_connections:调整 max_connections,依赖于 requested_open_files,即调整后的 open_files_limit。
- adjust_table_cache_size:调整 table_cache_size,依赖于调整后的 open_files_limit 和 max_connections。
- adjust_table_def_size:调整 table_definition_cache,依赖于调整后的 table_cache_size。
这四个参数中,最为复杂的是 open_files_limit 的调整,它涉及到系统对进程能打开的最大文件描述符的限制。
不妨,先基于调整后的 open_files_limit,看看后面三个参数的调整逻辑。
2.1 max_connections
首先,看看 max_connections 的取值逻辑
其中,
- requested_open_files:调整后的 open_files_limit。具体在本 Demo,是 2048。
- TABLE_OPEN_CACHE_MIN:常量,代表 table_cache_size 可允许的最小值。
#define TABLE_OPEN_CACHE_MIN 400
- max_connections:max_connections 的初始值。取配置文件中的配置,2000。如果配置文件中没有设置,则默认为 151。
基于代码中的计算逻辑,limit = 2048 – 10 – 400 * 2 = 1238。
1238 小于 2000,所以 max_connections 最后取值为 1238,与 Demo 开头的查询结果一致。
2.2 table_cache_size
接下来,看看 table_cache_size 的取值逻辑
其中,
- max_connections:max_connections 调整后的的值,即 1238。
- table_cache_instances:table_open_cache_instances。
- table_cache_size:table_cache_size 的初始值。在这个 Demo 中,是 1000。
基于代码中的计算逻辑,limit = max( ( 2048 – 10 – 1238 ) / 2,400 ) = 400。
400 小于 1000,所以,table_cache_size 最后取值为 400。
2.3 table_definition_cache
接下来,看看 table_definition_cache 的取值逻辑
同样,table_cache_size 也是调整后的值,即 400。
default_value = min( 400 + 400 / 2,2000) = 600。
顾名思义,default_value 是默认值。如果没有显式设置 table_definition_cache,则取默认值。
2.4 open_files_limit
最后看看 open_files_limit 的取值逻辑
因为是最先调整的参数,所以,这里的 max_connections,table_cache_size,open_files_limit 都是初始值。
基于代码中的计算逻辑,
- limit_1 = 10 + max_connections + table_cache_size _ 2 = 10 + 2000 + 1000 _ 2 = 4100
- limit_2 = max_connections _ 5 = 2000 _ 5 = 10000
- limit_3 = open_files_limit ? open_files_limit : 5000 = 65536
request_open_files = max(max(limit_1, limit_2), limit_3) = 65536,可理解为需要打开的文件数。
接下来,调用 my_set_max_open_files 函数获取 effective_open_files,后者是实际能打开的最大文件数。
看看 my_set_max_open_files 的处理逻辑。
相关的常量定义如下:
可见,在 Linux 系统中,MY_MIN(files, OS_FILE_LIMIT)还是等于 files,即 request_open_files。
这段代码里,重点还是调用 set_max_open_files 函数。
下面看看 set_max_open_files 的处理逻辑。
这里,使用了两个系统函数:getrlimit(),setrlimit(),分别用来获取和设置系统的资源限制(resource limit)。
其中,
- resource:资源类型。 可设置的资源类型有:RLIMIT_AS,RLIMIT_CORE,RLIMIT_CPU,RLIMIT_DATA,RLIMIT_FSIZE,RLIMIT_LOCKS,RLIMIT_MEMLOCK,RLIMIT_MSGQUEUE,RLIMIT_NICE,RLIMIT_NOFILE,RLIMIT_NPROC,RLIMIT_RSS,RLIMIT_RTPRIO,RLIMIT_RTTIME,RLIMIT_SIGPENDING,RLIMIT_STACK。 其中,RLIMIT_NOFILE 代表进程能打开的最大文件描述符数。
- rlimit:结构体,定义了两个变量。
struct rlimit {
rlim_t rlim_cur; /* Soft limit */
rlim_t rlim_max; /* Hard limit (ceiling for rlim_cur) */
};
其中,rlim_cur 代表软限制,rlim_max 代表硬限制。 注意:资源限制,真正起限制作用的是软限制,硬限制只是限制了软限制可设置的上限。
这两个函数,如果调用成功,则返回 0,反之,则返回-1。
了解完背景知识,接下来看看set_max_open_files具体的处理逻辑。
首先,获取进程的 RLIMIT_NOFILE。在此基础上,
这就意味着,在软限制大于等于 max_file_limit 的情况下:
接下来,就只有一种情况,即软限制小于max_file_limit。此时,会将max_file_limit赋值给软限制和硬限制,并尝试修改进程的RLIMIT_NOFILE。
这就意味着,在软限制小于 max_file_limit 的情况下:
什么情况下,能修改成功呢?
注意,这里的启动用户指的是启动 mysqld_safe 或 mysqld 的用户,不是运行用户(配置文件中指定的 user)。
既然 max_file_limit 的设置与进程的 RLIMIT_NOFILE 的有关,接下来,我们看看该进程的 RLIMIT_NOFILE。
其中,26424 是进程的 PID。
2048 是软限制,4096 是硬限制。
回到源码,启动用户是 mysql,2048 小于 65536,且 65536 大于 4096,所以不能修改进程的 RLIMIT_NOFILE。故而,最后,我们看到的 open_files_limit 是 2048。
同样的设置,如果 mysqld 是在 root 用户下启动呢?
竟然不是 65536,而是 100000。
3.1 启动用户
这个实际上与启动用户有关。用户决定了通过它启动的进程的资源上限,包括文件描述符数。
这里的上限,是在/etc/security/limits.conf 中定义的。如本 Demo 中的配置,
注意,这里虽然配置的是用户,但实际上限制的是通过用户启动的进程。
一般建议显式设置,如果不设置的话,则默认是 1024。1024,对于大多数应用来说,还是太小了。
用户可自由调整软限制和硬限制的大小,但需遵循以下规则:
除此之外,
3.2 启动脚本
除了启动用户,启动脚本也能限制进程的资源使用。
看下面这个 Demo,同样是在 root 用户下,只不过这次是通过 mysqld_safe 启动。
同样是在 root 用户下,如果是通过 mysqld_safe 启动呢?
竟然是 65536。
这背后,其实是 mysqld_safe 在”捣鬼”。
具体来说,如果配置文件或命令行中显式设置了 open_files_limit,则 mysqld_safe 在启动的过程中会通过”ulimit -n $open_files”调整进程的最大文件描述符数。
除了启动脚本,在 CentOS 7 中,如果是通过 systemd 管理进程,还可通过 LimitNOFILE 在进程级别设置文件描述符数的上限。
3.3 小结
进程能打开的最大文件描述符数由启动用户决定。除此之外,启动脚本(包括systemd),能在进程级别设置文件描述符数的上限。
文件描述符不够,在 MySQL 中的影响主要有:
4.1 常见原因
出现”Too many open files”,常见的原因有两个:
之所以要区分这两种场景,是因为两者的解决方法不一样。
4.2 解决方法
确定了原因,下面看看解决方法。
如果系统不支持在线调整进程的最大文件描述符数。就只能修改配置,重启实例。具体步骤如下:
无论是在线,还是通过/etc/security/limits.conf,调整进程的最大文件描述符数,在调整的时候,注意以下几点:
在 MySQL 状态变量中,没有能直观反映文件描述符的指标。
看下面这个 Demo。
open_files_limit 等于 1024,当文件描述符不足,出现”Too many open files”错误时,mysql 状态变量及 lsof 的统计结果如下:
其中,
- Innodb_num_open_files:InnoDB 当前打开的文件数。InnoDB 同时能打开的最大文件数由 innodb_open_files 参数决定,后者默认为-1,基于 table_cache_size 自动调整。 innodb_open_files 并不是一个硬限制。如果 InnoDB 打开的文件数达到了 innodb_open_files 的限制,则会通过 LRU 算法关闭其它文件。
if (innobase_open_files < 11) { innobase_open_files = 300; if (srv_file_per_table && table_cache_size > 300) { innobase_open_files = table_cache_size; } }
if (innobase_open_files > (long) open_files_limit) { ib::warn() << "innodb_open_files should not be greater" " than the open_files_limit.\n"; if (innobase_open_files > (long) table_cache_size) { innobase_open_files = table_cache_size; }}
* Open_files:当前打开的文件数。
* Opened_files:打开过的文件数。
从输出结果来看,这三个变量的值与 open_files_limit(1024)相去甚远,不如 lsof 的准确。
其中,9020 是进程的 PID。
lsof 同样适用于其它进程的统计。
Original: https://www.cnblogs.com/ivictor/p/14900930.html
Author: iVictor
Title: 从源码角度谈谈open_files_limit的生成逻辑及”Too many open files”的解决思路
原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/599401/
转载文章受原作者版权保护。转载请注明原作者出处!