故障分析:从Oracle数据库故障到Linux nproc算法

墨墨导读:本文来自墨天轮用户“你好我是李白”的投稿,使用root用户切换grid用户时报错-bash: fork: retry: Resource temporarily unava,这里记录故障处理全过程。

墨天轮主页:https://www.modb.pro/u/3997

某日,朋友跟我讨论他巡检oracle数据库时遇到的一个情况,在使用root用户切换grid用户时报错-bash: fork: retry: Resource temporarily unavailable,一般这个报错都是因为/etc/security/limits.conf或/etc/security/limits.d/下相关用户nproc设置过小导致,但是定位一波三折,最终了解清楚了nproc参数生成、限制,将案例详细分享,供大家参考。

故障背景

巡检su – grid无法完成切换,报错

-bash: fork: retry: Resource temporarily unavailable。

环境介绍

操作系统为Redhat 6.8,数据库版本为Oracle 11.2.0.4 RAC。

初步分析,获取已存在进程limits环境设置

根据经验,上述报错一般为下面三个原因:

  1. 用户的nproc达到限制,无法创建新的进程

  2. 系统没有可分配的的pid,即进程号已经达到内核参数kernel.pid_max的限制

  3. 系统可用内存低,新的进程无法申请到内存导致不能启动

下面我们一步一步排查:
检查用户已经存在进程limits设置与用户ulimit设置,检查如下:
如果已经有会话登陆grid用户,可以通过下面命令得到当前限制

如果已经无法正确切换grid用户,可以使用如下步骤得到限制

第一步:获取grid用户任意进程pid

第二步:使用cat查看/proc//limits获取限制设置

  • 可以看到上述无论是已经登陆grid还是真实进程设置均为16384,设置均不算过低,grid用户一般不会占用这么多的process,那到底是由于bug还是其他原因耗尽了设置呢还是内存或其他原因?

进一步分析,寻找limits.conf未生效原因

经过初步分析,初步判断并非设置过小导致,16384设置并不算小,RHEL默认/etc/sysctl.conf中内核参数kernel.pid_max为32768也足够使用,那接下来需要排查如下:

  1. /etc/security/limits.conf与/etc/security/limits.d是否设置过低 ?

  2. 是否/etc/profile、/etc/profile.d/、/etc/bashrc、家目录下.bashrc、.bash_profile是否有相关ulimit配置脚本设置nproc设置过低 ?

  3. 是否grid用户下真有超过16384 process占用导致无法su – grid ?

  4. 是否有进程real user不属于grid但是effective user为grid消耗了grid所有进程数?

接下来我们继续一步一步排查

经过排查,/etc/security/limits.conf设置如下:

可以看到limits.conf对grid用户做了ulimit相关设置,nproc设置为204800,经过排查,由于/etc/profile在安装RAC时按照文档设置了如下脚本导致,所以limits.conf的nproc相关配置并未生效

if [ $USER = "oracle" ]; then if [ $SHELL = "/bin/ksh" ]; then ulimit -u 16384 ulimit -n 65536 else ulimit -u 16384 -n 65536 fifi
if [ $USER = "grid" ]; then if [ $SHELL = "/bin/ksh" ]; then ulimit -u 16384 ulimit -n 65536 else ulimit -u 16384 -n 65536 fifi

获取根因,到底是谁占用了nproc

  • 要获取用户下的真是占用nproc,这里需要讲nproc到底是如何构成的

引用Redhat官网一段:

RLIMIT_NPROC The maximum number of processes (or, more precisely on Linux, threads) that can be created for the real user ID of the calling process. Upon encountering this limit, fork(2) fails with the error EAGAIN.
- nproc from /etc/security/limits.conf is to count the threads- Threads differ from traditional multitasking operating system processes in that:Raw * processes are typically independent, while threads exist as subsets of a process * processes carry considerably more state information than threads, whereas multiple threads within a process share process state as well as memory and other resources * processes have separate address spaces, whereas threads share their address space * processes interact only through system-provided inter-process communication mechanisms * context switching between threads in the same process is typically faster than context switching between processes.
At the kernel level, a process contains one or more kernel threads, which share the process's resources, such as memory and file handles – a process is a unit of resources, while a thread is a unit of scheduling and execution.
A process is a "heavyweight" unit of kernel scheduling, as creating, destroying, and switching processes is relatively expensive. Processes own resources allocated by the operating system. Resources include memory (for both code and data), file handles, sockets, device handles, windows, and a process control block.
A kernel thread is a "lightweight" unit of kernel scheduling. At least one kernel thread exists within each process. If multiple kernel threads can exist within a process, then they share the same memory and file resources.
  • 可以看到nproc是指thread,并不是process,也就是我们需要使用ps -L选项获取nproc占用总数

命令如下:

# ps -eLo ruser| sort |uniq -c|sort -rn注:按real user分类,nproc限制real user线程数,进程创建分为real user与effective user,我们统计real user。

我们经过查看当前服务器线程数,如下:

# ps -eLf|grep grid|wc -l44609注:当时未注意使用ruser统计真实线程数,所以上述44609实际要比真实grid用户占用的线程大很多,但是依然可以从中看出确实线程数占用很多,基本可以判断16384的nproc设置不足以支撑这么多会话导致su – grid时报错-bash: fork: retry: Resource temporarily unavailable

我们知道了确实是nproc不足导致无法su – grid,那到底是谁占用了这么多线程呢?
通过简单ps -ef输出,即可一眼判断下面截图进程异常暴涨导致:

根据经验,一般这种情况均由于bug或者一些配置方面问题导致,通过搜索Mos,发现对于ons -d进程异常暴涨有如下文章:

ONS has Thousand Processes Threads and Still Increasing (Doc ID 1547703.1).pdf
  • 文章意为,/etc/hosts在loopback地址行写入了hostname,导致ons -d成千且每分钟增长一个。

  • 检查/etc/hosts,发现确实将hostname写到了lookback内容行。

  • 至此,找到了原因,那解决就相对简单了,修改/etc/hosts,想减少ons -d还需要重启集群,减少ons -d,使进程恢复正常。

追根溯源,nproc是怎么计算的?

那么nproc是如何计算的呢,我们如何更合理的设置该值呢?

1. nproc控制

  • 文件:/etc/profile 、/etc/profile.d/下脚本、/etc/bashrc、家目录.bashrc、.bash_profile

  • 命令:ulimit设置。

2. 在不设置情况下默认值

可以从redhat官网文章:

What are the default ulimit values and where do they come from?

看到如下说明:

- On RHEL 6 and 7, nproc is unlimited for root by default (directly inherited from the kernel). For the other users, this value is equal to 4096, because it is limited at the PAM level by one of these files:/etc/security/limits.d/90-nproc.conf on RHEL 6/etc/security/limits.d/20-nproc.conf on RHEL 7
- On RHEL 8, these files have been removed, and the nproc default for the users is inherited from the kernel.- - 可以看到在不设置情况下,默认除root用户外,在RHEL6、RHEL7默认为4096。

3. nproc上限如何计算呢?

可以从Redhat官网文章

How is the nproc hard limit calculated

可以找到如下计算公式说明:

For nproc, the limit is calculated in the kernel before the first process is forked in kernel/fork.c called by start_kernel:
RLIMIT_NPROC = max_threads/2
- The value of these variables are:
-> max_threads = mempages / (8 * THREAD_SIZE / PAGE_SIZE); mempages comes from the function argument : fork_init(totalram_pages);-> #define THREAD_ORDER 2-> #define THREAD_SIZE (PAGE_SIZE << THREAD_ORDER)-> PAGE_SIZE = 4096 (but useless)

说明

公式为

RLIMIT_NPROC= max_threads/2

那么max_threads如何计算呢?

max_threads = mempages / (8 * THREAD_SIZE / PAGE_SIZE)

mempages为物理内存页数,可通过如下命令获得:

# dmesg | awk '/Total.pages:/ {print $NF}'

真实值也可通过下面命令获得:

# dmesg |grep Mem

thrad_size为线程尺寸,可通过如下计算公式获得:

THREAD_SIZE (PAGE_SIZE << THREAD_ORDER)

其中page_size可通过命令

# getconf PAGE_SIZE 获得,RHEL6 7均为4096

THREAD_ORDER在RHEL 6 7中均为2。

上述公式含义为:*将PAGE_SIZE转化为2进制,然后左移THREAD_ORDER位,即将4096转化为2进制,向左移动2位,2进制向左移动2位,即2^2=4。

所以公式演变为:

THREAD_SIZE=PAGE_SIZE*4

则最终RHEL6/RHEL7 x86_64中公式演变为

RLIMIT_NPROC= max_threads/2max_threads = mempages / (8 * THREAD_SIZE / PAGE_SIZE)RLIMIT_NPROC=( mempages / (8 * THREAD_SIZE / PAGE_SIZE))/2RLIMIT_NPROC=( mempages / (8 * PAGE_SIZE*4 / PAGE_SIZE))/2RLIMIT_NPROC=( mempages / (8 * 4 ))/2RLIMIT_NPROC= mempages / 64

计算示例:

# dmesg|grep Mem[ 0.000000] Memory: 4015580k/4718592k available (6764k kernel code, 524744k absent, 178268k reserved, 4433k data, 1680k init)

使用上述4015580代入公式得到如下值:

# echo "4015580/4/32/2"|bc15685# ulimit -Hu15685

可以看到确实计算一致,也就是当默认不设置limits.conf时,root用户默认的hard nproc为上述公式计算值。

注:真实情况下,由于内核版本不同,相关参数可能发生变化,也由于启动时,内存分配不同,会导致计算出来与实际值存在一些偏差,但是偏差不会很大。

4. 那使用内存计算的nproc是硬限制吗?

  • 通过测试,虽然使用内存计算出来的nproc是有限的,且相对更为合理,但是并不是说该值为硬限制,依然可以通过文件limits.conf、profile.d、limits.d、bashrc、ulimit等方式设置大于计算出来的值。

  • 但是一般建议通过计算设置更为合理,防止内存耗尽。

参考:
1. What are the default ulimit values and where do they come from?
2. How is the nproc hard limit calculated
3. ONS has Thousand Processes Threads and Still Increasing (Doc ID 1547703.1).pdf

作者

陈振海,4年数据库运维经验,擅长Oracle、MySQL,具有Oracle 11g OCM、OBCA等数据库认证,长期服务于公安、保险、税务等客户。

墨天轮原文链接:https://www.modb.pro/db/44563(复制到浏览器打开或者点击“阅读原文”立即查看)

(0)

相关推荐