分类目录归档:系统相关

绕过代理服务器获取客户端IP

一般来说,Web应用和客户端之间隔着很多个代理服务器,无论正向代理还是反向代理。这样就给本地资讯类应用(新闻、天气等)或者统计(审计,追踪等)需求带来了麻烦,因为在应用通过request.getRemoteAddr()方法(以Java为例)往往只能拿到与自己直接通信的设备IP。因此,如果用户通过直接或者间接的正向代理(ISP提供的缓存服务器等)上网,Web应用只会取到正向代理服务器地址;如果Web应用前端部署着Nginx或者Apache之类的反向代理,Web应用只会取到反向代理的地址。

面对这样的限制,代理厂商利用HTTP自定义Header规避了问题。比如Nginx,可以添加以下配置:

proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

它向后端应用传递了两个HTTP自定义Header,X-Real-IP和X-Forwarded-For。这两个Header作用类似,都是向后端透传客户端源IP,但是又有一些不同,主要区别在于:

1. X-Forwarded-For已有标准RFC文档(RFC 7239)定义,而X-Real-IP没有。
2. X-Forwarded-For Header格式为X-Forwarded-For: client1, proxy1, proxy2,每级代理都会将与自己直接通信的对端IP追加在X-Forwarded-For中,因此应用还需要额外解析IP列表以获取所需IP。而X-Real-IP只会记录与自己直接通信的对端IP。

由此,反向代理后面的应用服务器可以通过X-Real-IP或者X-Forwarded-For请求头来获取客户端IP:

String xRealIp = request.getHeader("X-Real-IP");
String xForwardIps = request.getHeader("X-Forwarded-For");

另外需要注意的是,HTTP Header是很容易被伪造的,因此,X-Forwarded-For Header中的首个IP也不意味着就一定是客户端源IP。从安全性方面来说,如果应用前不加反向代理,则request.getRemoteAddr()拿到的IP便是可信的,无法伪造;如果加了反向代理,那么X-Real-IP和X-Forwarded-For IP的最后一个IP是可信的,无法伪造。

--EOF--

小内存VPS站点内存使用调优(续)

昨天在『小内存VPS站点内存使用调优』中介绍了在VPS中打开swap分区,解决物理内存不足导致的OOM问题。本以为有了1G swap分区(SSD)做后盾,php-fpm的pm.*参数可以调大一点:

1
2
3
4
5
6
pm = dynamic
pm.max_children = 10
pm.start_servers = 5
pm.min_spare_servers = 3
pm.max_spare_servers = 5
pm.max_requests = 1000

结果今天DNSPod继续发来报警邮件,网站不可用,不过这次不可用的原因不是内存不足,而是IO太忙,Nginx提示504 Time-out。

先SSH到VPS上查看系统状态:
top-wa

上图是top命令的执行结果,异常表现在load很高(单核,10+),CPU使用率很低12.7%,大部分时间在等待IO,71.8% wa。

top命令看不出哪个进程在io wait。于是用apt-get安装iotop命令,它可以用来查看每个进程的IO情况:
iotop

通过左右箭头可以指定按某一列排序,按DISK READ列排序后,问题很直观了,清一色的php-fpm worker进程,并且都是99%左右的时间在swapin,所以问题定位在swap分区的使用不合理上。

swap分区本质是用磁盘模拟内存,它的IO特点是随机读写,即便是号称SSD,相比HDD延时低了不少,看来还是达不到系统使用要求。不知道DigitalOcean有没有对磁盘的IOPS进行Qos限制,从iotop上显示读速度62.85M/s来看,跟基准数据相比,处在偏下的水平。

定位出问题的原因之后,只能继续折腾php-fpm配置参数:

1
2
3
4
5
6
pm = dynamic
pm.max_children = 5
pm.start_servers = 3
pm.min_spare_servers = 2
pm.max_spare_servers = 3
pm.max_requests = 1000

另外,调整内核vm.swappiness参数(取值0-100,默认60)。swappiness数值越大,表示越优先使用swap;swappiness数值越低,表示越优先使用物理内存。

1
2
# echo 5 > /proc/sys/vm/swappiness
# echo vm.swappiness=5 >> /etc/sysctl.conf

把swappiness参数设置为5,告诉系统大部分时间优先分配物理内存,这可以解决极端情况下物理内存耗尽时,用swap分区做下缓冲。

初步看来,Nginx提示504 Timeout的问题解决了。

--EOF--

小内存VPS站点内存使用调优

本站托管在DigitalOcean,VPS规格是最低的1VCPU,512M内存,20G SSD存储。即便是对于这种日均PV不过百余的小站点,LNMP架构再加上顺便在主机里装上的一系列Shadowsocks、squid、l2tp等代理和VPN软件,内存逐渐成为最大瓶颈。

终于这两天DNSPod不断发邮件报警网站无法访问,打开网站提示“数据库连接错误”,ssh到主机里,dmesg提示各种OOM Killer:

# dmesg | grep oom
[.] php5-fpm invoked oom-killer: gfp_mask=0x201da,order=0,oom_adj=0,oom_score_adj=0
[.]  [<ffffffff810b77f3>] ? oom_kill_process+0x49/0x271
[.] [ pid ]   uid  tgid total_vm      rss cpu oom_adj oom_score_adj name
[.] mysqld invoked oom-killer: gfp_mask=0x201da,order=0,oom_adj=0,oom_score_adj=0
[.]  [<ffffffff810b77f3>] ? oom_kill_process+0x49/0x271
[.] [ pid ]   uid  tgid total_vm      rss cpu oom_adj oom_score_adj name
[.] php5-fpm invoked oom-killer: gfp_mask=0x280da,order=0,oom_adj=0,oom_score_adj=0
[.]  [<ffffffff810b77f3>] ? oom_kill_process+0x49/0x271
[.] [ pid ]   uid  tgid total_vm      rss cpu oom_adj oom_score_adj name
[.] php5-fpm invoked oom-killer: gfp_mask=0x201da,order=0,oom_adj=0,oom_score_adj=0
[.]  [<ffffffff810b77f3>] ? oom_kill_process+0x49/0x271
[.] [ pid ]   uid  tgid total_vm      rss cpu oom_adj oom_score_adj name

php5-fpm和mysqld进程频繁被杀,php5-fpm进程实际上是fastcgi worker进程,即使被杀,也能被php-fpm master进程重启,因此不影响网站可用性,但是mysqld进程一旦被杀了,数据库就挂了,导致首页出现“数据库连接错误”提示。

低层次的内存使用优化可以从两方面入手:

1. 降低mysql的oom score权重,降低被OOM Killer选中的几率。

这点可参考『Linux OOM Killer问题』,将mysqld进程的oom_score_adj调整成最小值-1000:

1
2
# pid=$(cat /var/run/mysqld/mysqld.pid)
# echo -1000 > /proc/$pid/oom_score_adj

2. 减少php-fpm的内存使用量。

Nginx作为Web Server,本身不具备解释执行php的能力,我们能通过GET /index.php获得HTML响应的原因是Nginx后端对接了php解释程序,这个对接的协议就是fastcgi(早期使用cgi,因为fork-exec的请求分发执行方式效率太低,后来有了fastcgi,采用对象池模型,避免每次解析配置文件、初始化执行环境等),fastcgi与语言无关,php-fpm就是php语言环境下的一个fastcgi实现。关于cgi、fastcgi、php-fpm之类的讨论可以参考这两篇文章: 『FastCgi与PHP-fpm之间的关系』,『概念了解:CGI,FastCGI,PHP-CGI与PHP-FPM』。

默认情况下,php-fpm中关于进程池的配置(/etc/php5/fpm/pool.d/www.conf)如下:

1
2
3
pm = dynamic ;;表示worker进程动态调整。
pm.max_children = 20 ;;表示最大worker进程数量。
pm.max_requests = 0 ;;表示每个worker进程最多处理多少次请求后重启,0表示永不重启。

从中可以看出默认配置对于小内存(512M)的VPS的不合理之处,每个cgi worker进程的初始内存占用量约为10M,并且很容易就涨至50+M,当并发数稍稍上去一点,可能就会产生 20(workers) * 50M ~= 1G 的内存使用量。这还未考虑某些php第三方库存在内存泄露问题,当pm.max_requests参数为0,内存泄露会造成worker进程内存使用量越用越大,最终失控。

权衡之后,我把配置调整为:

1
2
3
4
5
6
pm = dynamic
pm.max_children = 3
pm.start_servers = 2 ;;启动worker数量
pm.min_spare_servers = 2 ;;最小空闲worker数量
pm.max_spare_servers = 3 ;;最大空闲worker数量
pm.max_requests = 500 ;;每个worker处理500次请求后重启

虽然保守了一点,但至少比较保险。

调整pm.*参数时建议taif syslog和php5-fpm.log,实时监控站点运行状况。tailf syslog目的是为了监控还有没有php-fpm进程被oom kill;tailf php5-fpm.log目的是为了查看fpm worker进程的启动和异常关闭情况。如果调整后出现Nginx “502 bad gateway”错误,或者php-fpm频繁的“WARNING: [pool www] child 10595 exited on signal 9 (SIGKILL) after 78.871109 seconds from start”日志,那肯定是pm.*参数调整不合理无疑了。

以上2种手段毕竟治标不治本,是以牺牲性能为前提的。实际上当内存不足时,最直观也是最简单的方法当然是加内存,既然物理内存没法加,那就加虚拟内存(swap)吧,DigtalOcean上的VPS主机默认没有开swap分区:

1
2
3
4
5
6
7
# free -h
             total       used       free     shared    buffers     cached
Mem:          497M       408M        88M         0B       456K        15M
-/+ buffers/cache:       393M       103M
Swap:            0          0          0
# swapon -s
Filename                                Type            Size    Used    Priority

DigitalOcean提供了SSD硬盘,所以开个2倍物理内存大小的swap分区也没什么问题,以下是使用磁盘文件作为swap分区的步骤:

1. 生成1G大小的磁盘文件

1
# dd if=/dev/zero of=/swapfile bs=1M count=1024

2. 将文件格式化为swap分区

1
# mkswap /swapfile

3. 启动swap分区

1
2
3
4
5
6
7
8
9
# swapon /swapfile
# free -h
             total       used       free     shared    buffers     cached
Mem:          497M       462M        34M         0B       252K        21M
-/+ buffers/cache:       440M        56M
Swap:         1.0G          0       1.0G
# swapon -s
Filename                                Type            Size    Used    Priority
/swapfile                               file            1048572 645840  -1

4. 将swap分区加到/etc/fstab,使重启后仍生效

1
# echo /swapfile swap swap defaults 0 0 >> /etc/fstab

有了1G的基于SSD的swap分区后,就可以把php-fpm参数调得任性一点了:

1
2
3
4
5
6
pm = dynamic
pm.max_children = 10
pm.start_servers = 5
pm.min_spare_servers = 3
pm.max_spare_servers = 5
pm.max_requests = 1000

初步看来,小内存VPS站点内存耗尽问题解决了。

--EOF--

aufs文件系统

aufs是一种实现了联合挂载(union mount)的文件系统,同unionfs类似,它能够将不同类型的文件系统透明地层叠在一起,实现一个高效的分层文件系统。说白了aufs就是能将不同的目录挂载到某一目录下,并将各个源目录下的内容联合到目标目录下,这里每个源目录对应aufs中的一层,用户在目标目录读写时,感觉不到此目录是联合而来的。aufs中的每一层都可以有不同的权限(只读,读写),这个特性使得它在很多开源项目中有应用,比如Knoppix,Live CD, Docker等等。

Linux上unionfs的实现依托了VFS设计的灵活性,从架构上看,它在VFS的下层,在具体文件系统(如ext3, ext4等)的上层。系统调用read()/write()落在VFS上,VFS找到待操作文件的inode(unionfs inode),经过unionfs的路由后找到真实文件的inode,执行操作。这是一个hook的过程。具体实现可以参考这两篇关于unionfs实现的文章:Part1, Part2

0x00: 环境准备

aufs默认没有合入内核,据说被拒绝多次,此生无望。debian下可以通过apt安装:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# apt-get install -y aufs-tools
# dpkg -l | grep aufs-tools
ii  aufs-tools    1:3.0+20120411-2    amd64    Tools to manage aufs filesystems
# pwd
/root/test
# mkdir a b uniondir
# echo 1 > a/1.txt
# echo 2 > b/2.txt
# tree .
.
├── a
│   └── 1.txt
├── b
│   └── 2.txt
└── uniondir

0x01: 挂载,文件操作

1
# mount -t aufs -o br=/root/test/a:/root/test/b none /root/test/uniondir

aufs里每个源目录都是一个branch,挂载过程就是对不同branch进行联合操作,当不同分支内容相同时,上层覆盖下层。mount命令中,-t表示目标文件系统类型,-o表示挂载参数, none表示挂载的不是设备文件。这些都是mount命令的参数。br是aufs的参数,表示分支,多个分支之间用冒号(:)分隔,由于分支之间在逻辑上是层叠的,因此声明在前的表示逻辑上层,声明在后的表示逻辑上层,示例中a目录覆盖b目录。mount命令执行后,当前文件系统状态如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
# mount -v
sysfs on /sys type sysfs (rw,nosuid,nodev,noexec,relatime)
......
none on /root/test/uniondir type aufs (rw,relatime,si=73990447ced17254)
# tree .
.
├── a
│   └── 1.txt
├── b
│   └── 2.txt
└── uniondir
    ├── 1.txt
    └── 2.txt

可见,a、b目录的内容都被联合到uniondir目录下,此时uniondir目录就是一个aufs文件系统类型的挂载点,从df命令也可以验证。

因为在mount br参数里没加读写权限,默认情况下最上层分支可读写,其他层只读,所以,在uniondir目录下创建3.txt,实际上会创建到最上层分支里(a目录):

1
2
3
4
5
6
7
8
9
10
11
12
# echo 3 > /root/test/uniondir/3.txt
# tree .
.
├── a
│   ├── 1.txt
│   └── 3.txt
├── b
│   └── 2.txt
└── uniondir
    ├── 1.txt
    ├── 2.txt
    └── 3.txt

因为读写权限的不同,修改1.txt和2.txt内容对a、b的影响也不同:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# echo 'add sth' >> /root/test/uniondir/1.txt
# echo 'add sth' >> /root/test/uniondir/2.txt
# tree .
.
├── a
│   ├── 1.txt
│   ├── 2.txt
│   └── 3.txt
├── b
│   └── 2.txt
└── uniondir
    ├── 1.txt
    ├── 2.txt
    └── 3.txt
# cat /root/test/a/1.txt
1
add sth
# cat /root/test/a/2.txt
2
add sth
# cat /root/test/b/2.txt
2

可见,对可读写分支(目录a),修改直接作用到分支上;而对于只读分支(目录b),修改操作会触发一次写时复制(COW, Copy On Write),即先将待修改文件从目录b复制到目录a,然后在目录a修改相应文件。注意写时复制机制可能在修改大文件时带来性能损耗,虽然每个文件最多只会复制一次。

最后验证下aufs文件系统的删除操作:

1
2
3
4
5
6
7
# rm /root/test/uniondir/*
# tree .
.
├── a
├── b
│   └── 2.txt
└── uniondir

所有的删除操作也都是在最上层的可读写层进行,目录b因为是只读层,所以不受影响。但是为什么最终的uniondir目录中不见2.txt文件呢?因为最上层对它进行了遮挡:

1
2
3
4
5
# ls -al /root/test/a
drwxr-xr-x 4 root root 4096 Jan 17 20:08 .
drwxr-xr-x 5 root root 4096 Jan 17 19:20 ..
-r--r--r-- 2 root root    0 Jan 17 19:27 .wh.2.txt
......

aufs通过在上层加“.wh.+文件/目录名”的特殊标记来遮挡下层的文件。

0x02: 高级参数

1. aufs在mount的时候可以指定每个分支的读写权限。比如:

1
# mount -t aufs -o br=a=rw:b=rw:c=ro none /root/test/uniondir

这个挂载选项指定了a分支和b分支为可读写,c目录为只读。每个分支的读写权限记录在/sys/fs/aufs/si_XXX/brNNN文件里,这里,XXX可从mount -v命令中得到,NNN从0开始,表示第一层,往上递增,依次类推。

1
2
3
4
5
6
# ls /sys/fs/aufs/si_73990447c8980254/
br0  br1  br2  xi_path
# cat /sys/fs/aufs/si_73990447c8980254/br*
/root/test/a=rw
/root/test/b=rw
/root/test/c=ro

当有两个及以上的读写层时,在aufs挂载目录中新建文件就会涉及到源目录的选择策略问题,aufs提供了create配置参数,可以指定各种策略(round-robin(rr), most−free−space(mfs), etc),默认情况下只会写到第一层可写层。另外,经过测试发现,aufs文件系统最上层一定是要可读写的,否则会造成此文件系统只读。

2. aufs支持udba参数,用于配置是否监听源目录(branch)的变化。如果需要源目录的修改实时反应到挂载目录,设置挂载参数-o udba=reval,如果对源目录的修改不需反应到挂载目录,设置挂载参数-o udba=none。默认为reval。

0x03: 限制

aufs出于某些原因会限制层叠的分支数量,debian里默认是127层(早期是42层)。首先,层数越多,效率肯定越低。另一方面,由于/proc和sysfs文件系统中有单个文件4k大小的限制,所以依赖于此的的aufs元信息写入就会受影响,无论层次太多还是分支名称过长,都很容易达到这个限制(brs参数为0时,aufs会将所有的分支信息写入到/proc/mount文件里)。要突破这个限制,可以选择重新编译aufs模块,enable CONFIG_SYSFS和设置brs参数为1(貌似debian下的aufs包默认就是开启此功能的),并且设置合适的编译参数(kconfig中CONFIG_AUFS_BRANCH_MAX_32767=y),这种绕过的方法未经测试,理论上应该可行。

拓展阅读:
1. aufs manpage. http://aufs.sourceforge.net/aufs3/man.html
2. aufs changelog. http://aufs.sourceforge.net/
3. aufs tutorial. http://www.thegeekstuff.com/2013/05/linux-aufs/

--EOF--

Linux OOM Killer问题

上周有一个线上程序挂了,由于早就知道服务器存在内存不足的情况,因此大概猜测问题应该出在Linux的OOM Killer上,grep了一把syslog,找到真凶,证实了我的猜测:

1
2
3
4
5
6
7
8
/var/log/messages:Nov 10 09:41:53 kernel: [8358409.054]java invoked oom-killer:
gfp_mask=0x201da, order=0, oom_adj=0, oom_score_adj=0
/var/log/messages:Nov 10 09:41:53 kernel: [8358409.054] [<ffffffff810b799b>] ?
oom_kill_process+0x49/0x271
/var/log/messages.1:Nov  9 06:29:19 kernel: [8260455.112]ntpd invoked oom-killer:
gfp_mask=0x201da, order=0, oom_adj=0, oom_score_adj=0
/var/log/messages.1:Nov  9 06:29:19 kernel: [8260455.112] [<ffffffff810b799b>] ?
oom_kill_process+0x49/0x271

Linux的内存管理存在超售现象,也就是Overcommit策略。举个栗子,当我们用C语言的malloc分配了内存之后,只要指定的内存大小不超过进程的虚拟内存大小,一般都会返回成功,只有程序在使用这块内存时,操作系统才会真正分配给它,比如调用memset()函数填充内存时。在32位操作系统下,进程的虚拟内存是4G,低3G为用户空间,高1G为内核空间,所以程序一次性可以malloc 3G以下的内存而不报错,即使当前的物理内存远远小于这个值。这样必然出现一种情况,当系统中所有进程占用的实际内存大于物理内存和swap分区之和时,Linux需要选择一个策略来处理这个问题,策略的选择就是修改Overcommit参数。

当Overcommit策略启用时,并且此时物理内存已经耗尽而又有程序需要分配内存时,Linux就会触发OOM Killer,它可以简单理解为操作系统选择一些进程杀掉,腾出内存空间。选择被杀进程的算法还是蛮讲究的,既要考虑进程重要性,比如是否root执行、是否系统进程、nice值等,又要兼顾回收效益,比如最好应该通过杀掉最少的进程来回收最多的内存。这方面,不同的内核版本也会有所区别,大概思路如2.4版源码注释所说:

1
2
3
4
5
6
7
1) we lose the minimum amount of work done
2) we recover a large amount of memory
3) we don't kill anything innocent of eating tons of memory
4) we want to kill the minimum amount of processes (one)
5) we try to kill the process the user expects us to kill, this
   algorithm has been meticulously tuned to meet the priniciple
   of least surprise ... (be careful when you change it)

注: 源码路径linux/mm/oom_kill.c,函数调用链为out_of_memory() -> oom_kill() -> select_bad_process() -> badness(),上述注释来自badness()函数。

选择被杀进程的难度,就好比社会学意义上判定一个人死刑一样,无论这个人有多少个该死的理由,但是总能找出一些这个人的存在价值。对应到应用程序,某个应用程序可能吃内存最多,但是同时它对某些业务来说最不可或缺,比如RabbitMQ,Redis,MySQL等等。于是Linux为每个进程都提供了一个可以免死的机会,就是/proc/<pid>/oom_score_adj参数。

每个进程都有一个oom_score值(位于/proc/<pid>/oom_score,总是大于等于0),这个值越大就越容易被OOM Killer杀掉,同时/proc/<pid>/oom_score_adj值(取值范围-1000~1000)可以对oom_score值进行调整,比如oom_score_adj值为-10,则会将最终的oom_score值减10。

做个实验吧,实验环境为:VPS,单核,RAM 128M, swap 256M, 32位debian6,kernel 2.6.32。

1
2
3
4
5
6
7
8
# vi oomscore.sh
#!/bin/bash
for proc in $(find /proc -maxdepth 1 -regex '/proc/[0-9]+'); do
    printf "%2d %5d %s\n" \
        "$(cat $proc/oom_score)" \
        "$(basename $proc)" \
        "$(cat $proc/cmdline | tr '\0' ' ' | head -c 50)"
done 2>/dev/null | sort -nr | head -n 10

这个oomscore.sh脚本可以显示当前oom_score排名前10的进程oom_score,pid和进程名等,借鉴来辅助观察。

首先执行oomscore.sh脚本,得到当前omm_score排名前10的进程,以及内存使用情况:

1
2
3
4
5
6
7
8
9
10
# sh oomscore.sh 
67 17976 /usr/sbin/apache2 -k start 
40   695 /usr/sbin/mysqld --basedir=/usr --datadir=/var/lib
 7 18616 sshd: root@pts/6  
.......
# free -m
             total       used       free     shared    buffers     cached
Mem:           128         75         52          0          0         26
-/+ buffers/cache:         49         78
Swap:          256         15        240

然后跑以下测试程序alloc1mps.c,每秒分配1M内存:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
 
#define M 1024*1024
int main(){
        int count = 1;
        while(1){
                char* mem = (char*)malloc(sizeof(char) * M);
                if(!mem){
                        break;
                }
                memset(mem, 0, M);
                printf("alloc %d M\n", count++);
                sleep(1);
        }
        printf("mem exhausted!\n");
        return 0;
}

通过sh omm_score.sh,top,free -m,dmesg等命令观察内存分配和omm_score关系,可以得到以下现象和结论:
1. 首先从分配物理内存中分配空间,等物理内存分配得差不多了,-/+ buffers/cache free趋于0时,开始从swap分配内存。
2. 当swap分区内存接近耗尽时,此时alloc1mps进程的omm_score值非常大。
3. 内存(物理内存+swap)耗尽,触发omm killer回收内存,alloc1mps进程被杀,dmesg证实这一点。

1
2
3
4
5
6
7
8
9
10
11
12
13
# free -m
             total       used       free     shared    buffers     cached
Mem:           128        124          3          0          0          0
-/+ buffers/cache:        123          4
Swap:          256        249          6
# sh oom.sh 
791 19412 ./alloc1mps 
56 17976 /usr/sbin/apache2 -k start 
36   695 /usr/sbin/mysqld --basedir=/usr --datadir=/var/lib
......
# dmesg
[235669.855]Out of memory in UB 35782: OOM killed process 19412 (alloc1mps)
score 0 vm:318156kB, rss:119476kB, swap:196868kB

重跑测试程序,当alloc1mps开始运行后,调整其omm_score_adj参数,设为-800。

1
2
3
4
# Pid=` ps axu | grep alloc1mps | grep -v 'grep' | awk '{print $2}'`
# echo -800 > /proc/${Pid}/oom_score_adj
# cat  /proc/${Pid}/oom_score_adj
-800

继续观察,这次发现内存接近耗尽时,受oom_score_adj影响,alloc1mps进程的oom_score值低于apache2和mysqld进程,因此首先被OOM Killer杀掉的是apache2进程。apache2进程被kill后,alloc1mps继续执行,它的oom_score值也慢慢涨上来了,下一个被kill的就是它了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# free -m
             total       used       free     shared    buffers     cached
Mem:           128        122          5          0          0          0
-/+ buffers/cache:        121          6
Swap:          256        252          3
# sh oom.sh 
36   695 /usr/sbin/mysqld --basedir=/usr --datadir=/var/lib
33 21288 ./alloc1mps 
 2 22233 sh oom.sh 
......
# dmesg
[236432.377]Out of memory in UB 35782: OOM killed process 17976 (apache2)
score 0 vm:55760kB, rss:4kB, swap:22284kB
[236455.623]Out of memory in UB 35782: OOM killed process 21288 (alloc1mps)
score 0 vm:339744kB, rss:118540kB, swap:219132kB

了解了OOM Killer机制后,想要规避重要进程被kill的命运,就只能在进程运行时调整其omm_score_adj参数了,这种调整是临时的,当系统重启后会清零。所以,最根本的解决方法还是加内存。

--EOF--