Inode 节点占满

查看磁盘占用:

$ df -i
Filesystem            Inodes   IUsed   IFree IUse% Mounted on
/dev/vda1            1310720 1310720       0  100% /
tmpfs                 983191       1  983190    1% /dev/shm
/dev/vdb             13107200   91566 13015634    1% /data

$ df -h
Filesystem            Size  Used Avail Use% Mounted on
/dev/vda1              20G  9.0G  9.8G  48% /
tmpfs                 3.8G     0  3.8G   0% /dev/shm
/dev/vdb              197G  131G   57G  71% /data

找出文件数量最多的目录:

$ for i in /*; do echo $i; find $i | wc -l; done

删除文件:

$ find dir -type f -name '*'  | xargs rm

$ find /var/spool/clientmqueue -type f -mtime +30 -exec rm -f {} \;

进程跑满

错误:

-bash: fork: retry: No child processes

查看进程数限制:

$ cat /proc/sys/kernel/pid_max
49152

调整限制:

$ echo 100000 > /proc/sys/kernel/pid_max

或者查看是否有无用的进程:

$ pstree -up

删除文件未释放磁盘占用

$ lsof | grep delete

$ find /proc/*/fd -ls | grep  '(deleted)'
$ : > "/proc/$pid/fd/$fd"
#!/bin/bash

delete_files=$(find /proc/*/fd -ls 2>/dev/null | grep  '(deleted)' | awk '{print $11}')
for delete_file in ${delete_files}; do
    echo "echom "" > ${delete_file}"
    : > ${delete_file}
done

free 解释

$ free -g
             total       used       free     shared    buffers     cached
Mem:           125        122          3          0          0         99
-/+ buffers/cache:         22        102
Swap:            0          0          0
  • total: 总内存大小
  • used: 已经使用的内存
  • free: 空闲的内存
  • shared: 多个进程共享的内存总额
  • buffers: 块缓存(buffer cache)大小,缓存块设备的块数据(如磁盘)
  • cached: 页缓存(page cache)大小,缓存文件的页数据
  • -buffers/cache: 已用的内存数,不包含磁盘缓存,used - buffers - cached
  • +buffers/cache: 可用的内存数,包含磁盘缓存,free + buffers + cached

这里的 buffers, cached 是磁盘缓存,这部分内存也可以被进程申请到,所以计算可用的内存时,应该用 +buffers/cache,即 free + buffers + cached

实际被进程占用的内存,应该去除磁盘缓存,即 -buffers/cache,即 used - buffers - cached

在 Linux 2.4 版本的内核之前,page cache 与 buffer cache 是完全分离的。但是,块设备大多是磁盘,磁盘上的数据又大多通过文件系统来组织,这种设计导致很多数据被缓存了两次,浪费内存。所以在 2.4 版本内核之后,两块缓存近似融合在了一起:如果一个文件的页加载到了 page cache,那么同时 buffer cache 只需要维护块指向页的指针就可以了。只有那些没有文件表示的块,或者绕过了文件系统直接操作(如dd命令)的块,才会真正放到 buffer cache 里。因此,我们现在提起 page cache,基本上都同时指 page cache 和 buffer cache 两者。

page_cache

linux 使用 page cache 增加对磁盘访问的性能,page cache 在内存中缓存了磁盘上的部分数据。

  1. 对读请求,系统首先会检查所读页面是否在 cache 中:
    1. 如果在 cache 中,那么直接从内存中读取,不需要访问磁盘。
    2. 如果不在 cache 中,那么从磁盘中读取所请求的页面,并 预读 几个相邻的页面(局部性原理),缓存在 cache 中。
  2. 对写请求,数据直接写入 cache,磁盘内容不会直接更新(断电有丢失数据风险)写入的 page 被标记为 dirty,内核会周期的将 dirty page 写回 磁盘。

相关参数:

# dirty page 大小达到多少字节后开始触发刷磁盘
vm.dirty_background_bytes = 0

# dirty page 内存占比超过 dirty_background_ratio 后开始触发刷磁盘(默认 10%)
vm.dirty_background_ratio = 10

# dirty page 大小达到多少字节后停止接收写请求,开始触发刷磁盘
vm.dirty_bytes = 0

# dirty page 内存占比超过 dirty_ratio 后停止接收写请求,开始触发刷磁盘(默认 30%)
vm.dirty_ratio = 30

# 存在时间超过 dirty_expire_centisecs 的 dirty page 会被周期性刷盘操作写会磁盘(默认 30秒)
vm.dirty_expire_centisecs = 3000

# 多长时间唤醒一次周期性的刷盘操作(默认 5秒)
vm.dirty_writeback_centisecs = 500

清除 cache

仅清除页面缓存(PageCache)

$ sync; echo 1 > /proc/sys/vm/drop_caches

清除目录项和inode

$ sync; echo 2 > /proc/sys/vm/drop_caches

清除页面缓存,目录项和inode

$ sync; echo 3 > /proc/sys/vm/drop_caches

当内存小于 90G 时自动清理:

#!/bin/bash

#
# /etc/cron.hourly/drop_caches.sh
#

free_mem=$(free -g | awk '/^Mem/ {print $4}')
echo "$(date +"%Y-%m-%d-%T") - before free_mem: $free_mem" >> /root/drop_caches.log 2>&1

if [[ free_mem -lt 90 ]]; then
  echo "$(date +"%Y-%m-%d-%T") - drop caches" >> /root/drop_caches.log 2>&1
  sync; echo 1 > /proc/sys/vm/drop_caches
else
  echo "$(date +"%Y-%m-%d-%T") - do  nothing" >> /root/drop_caches.log 2>&1
fi

free_mem=$(free -g | awk '/^Mem/ {print $4}')
echo "$(date +"%Y-%m-%d-%T") -  after free_mem: $free_mem" >> /root/drop_caches.log 2>&1

messages

$ cat /var/log/messages
...
kernel: SLUB: Unable to allocate memory on node -1 (gfp=0x8020)
...

ulimit

ulimit 控制 shell 程序资源

ulimit [-aHS] \
    [-c <core文件上限>] \
    [-d <数据节区大小>] \
    [-f <文件大小>] \
    [-m <内存大小>] \
    [-n <文件数目>] \
    [-p <缓冲区大小>] \
    [-s <堆栈大小>] \
    [-t <CPU时间>] \
    [-u <程序数目>] \
    [-v <虚拟内存大小>]
  • -H 设置硬件资源限制,是管理员所设下的限制
  • -S 设置软件资源限制,是管理员所设下的限制
  • -a 显示当前所有的资源限制
  • -u num: 用户最多可启动的进程数目
  • -c size: 设置core文件的最大值 单位:blocks
  • -d size: 设置程序数据段的最大值 单位:kbytes
  • -f size: 设置shell创建文件的最大值 单位:blocks
  • -l size: 设置在内存中锁定进程的最大值 单位:kbytes
  • -m size: 设置可以使用的常驻内存的最大值 单位:kbytes
  • -n num: 设置内核可以同时打开的文件描述符的最大数目
  • -p size: 设置管道缓冲区的最大值 单位:kbytes
  • -s size: 设置堆栈的最大值 单位:kbytes
  • -t size: 设置CPU使用时间的最大上限 单位:seconds
  • -v size: 设置虚拟内存的最大值 单位:kbytes

内核可以同时打开的文件描述符默认是1024,在高负载下要设置为更高,可以通过以下命令修改

$ ulimit -SHn 65535

ulimit只能做临时修改,重启后失效。

可以加入到 /etc/rc.local 文件,每次启动启用。

可以通过修改 /etc/security/limits.conf 永久调整 Linux 系统的最大进程数和最大文件打开数限制,

#<domain>        <type>  <item>  <value>
* soft nproc 11000
* hard nproc 11000
* soft nofile 655350
* hard nofile 655350

同样需要注意 /etc/security/limits.d 目录下的文件,目录下文件与 /etc/security/limits.conf 有相同 domain 的配置时,会覆盖 /etc/security/limits.conf 中的配置

cat /proc/29097/limits

prlimit -n4096 -p pid_of_process
  • https://stackoverflow.com/questions/3734932/max-open-files-for-working-process

sysctl

vm.max_map_count

查看 vm.max_map_count 设置:

$ sysctl vm.max_map_count
vm.max_map_count = 65530

修改 vm.max_map_count 设置:

$ sysctl -w vm.max_map_count=262144

编辑 /etc/sysctl.conf 永久修改 vm.max_map_count 参数:

$ echo 'vm.max_map_count = 262144' >> /etc/sysctl.conf
$ sysctl -p
vm.max_map_count = 262144

syn/accept queue

                   客户                          服务器
              socket|                              |socket, bind, listen
                    |                              |
                    |                              |LISTEN(被动打开)
                    |                              |
                    |                              |accept(阻塞)
                    |                              |
      connect(阻塞) |-----------SYN J------------->|SYN_RCVD      -> 进入 SYN Queue
  (主动打开)SYN_SENT|                            / |
                    |                           /  |
         ESTABLISHED|<------SYN K, ACK J+1-----+   |
       connect(返回)|-----------ACK K+1 ---------->|ESTABLISHED   -> 进入 Accept Queue
                    |                              |
                    |                              |accept(返回)  <- 从 Accept Queue 取 socket
                    |                              |

客户端连接 tcp 服务端需要 3 次握手,tcp 服务端有 2 个独立的队列:

  1. SYN Queue: tcp 服务端收到第一个 syn 时,连接处于 SYN_RCVD 状态,并加入到 syn queue 中。随后 tcp 服务端响应 syn,ack,此时 socket 被称为半连接 socket
  2. Accept Queue: 当服务端收到响应的 ack 后,连接处于 ESTABLISHED 状态,将连接从 syn queue 移除,加入到 accept queue。此时 socket 为已连接 socket,服务端阻塞的 accept() 函数会从 accept queue 取 established socket。

内核给监听端口分配 syn queue 和 accept queue,队列的大小由 listen() 系统调用的 backlog 参数决定:

int listen(int sockfd, int backlog)

另外,队列大小还与 net.core.somaxconn, net.ipv4.tcp_max_syn_backlog 系统参数有关:

  • max accept queue size = min(backlog, net.core.somaxconn)
  • max syn queue size = min(backlog, net.core.somaxconn, net.ipv4.tcp_max_syn_backlog)

对于 syn queue,还与参数 net.ipv4.tcp_syncookies 相关:

  • net.ipv4.tcp_syncookies=0: 关闭 syncookies,当 syn queue 满了之后,新的连接无法建立。
  • net.ipv4.tcp_syncookies=1: syn queue 满时启用 syncookies,当 syn queue 满了之后,server 仍可以处理新接收到的 syn 包并返回 syn,ack,返回的包中携带服务端生成的隐藏信息,当接收到客户端的 ack 后,验证 ack,通过验证之后 3 次握手完成,连接成功建立。
  • net.ipv4.tcp_syncookies=2: 始终采用 syncookies 连接机制。

对于 accept queue,还与参数 net.ipv4.tcp_abort_on_overflow 相关:

  • net.ipv4.tcp_abort_on_overflow = 0: accept queue 满后,收到新的 ack 直接被丢弃,客户端返回 read timeout 错误
  • net.ipv4.tcp_abort_on_overflow = 1: accept queue 满后,收到新的 ack 后响应 RST 给客户端,客户端返回 connection reset by peer 错误

通过查看 9094 端口监听 socket 的 accpet queue 大小:

$ ss -ltpn sport = :9094 | cat
Recv-Q Send-Q             Local Address:Port               Peer Address:Port
0      50                            :::9094                         :::*      users:(("java",34965,4099))

这里 Send-Q 显示队列的最大大小,Recv-Q 显示当前处于 accept queue 队列中 socket 的数量

参考:

  • https://zhuanlan.zhihu.com/p/102502913
  • https://blog.cloudflare.com/syn-packet-handling-in-the-wild/

keepalive

/etc/sysctl.conf

# 空闲时长/发送心跳的周期 7200s
net.ipv4.tcp_keepalive_time=7200
# 探测包发送间隔 75s
net.ipv4.tcp_keepalive_intvl=75
# 达到 tcp_keepalive_time 后,没有接收到对方确认,继续发送探测包次数
net.ipv4.tcp_keepalive_probes=9

overcommit_memory

  • vm.overcommit_memory=0:只要有空闲的物理内存,就允许给进程分配,会适度超发内存
  • vm.overcommit_memory=1:内核在分配的时候不做检查,进程真正使用的时候,在pagefault里可能会失败
  • vm.overcommit_memory=2:允许分配不超过所有物理内存和交换空间总和的内存,上限是 swap + 物理mem * ratio
    • vm.overcommit_memory=2,vm.overcommit_ratio=90

查看参数:

$ sysctl vm.overcommit_memory

临时修改参数值:

$ sysctl -w vm.overcommit_ratio=90

编辑文件 /etc/sysctl.conf 修改 vm.overcommit_memory,然后执行 sysctl -p 永久生效配置

连接过多丢包

/var/log/messages

Jan 17 11:12:09 xxg-udw38 kernel: [107394376.115531] nf_conntrack: table full, dropping packet.

/etc/sysctl.conf

If the problem is caused by a large number of connections, try to increase the value of net.ipv4.netfilter.ip_conntrack_max:

sysctl -w net.ip4.netfilter.ip_conntrack_max=655360 //RHEL 5
sysctl -w net.netfilter.nf_conntrack_max=655360 //RHEL 6+

To make the changes permanent, add the following in /etc/sysctl.conf:

net.ip4.netfilter.ip_conntrack_max=655360 //RHEL 5
net.netfilter.nf_conntrack_max=655360 //RHEL 6+

As a general best practice, do not allow too many connections on a server. A conntrack consumes 400 bytes in the kernel (see /proc/slabinfo). This means tracking 655360 connections would consume 262MB of RAM.

for i in `docker ps | awk 'NR>1 {print $NF}'`; do
    echo $i;
    docker exec $i cat /proc/sys/net/netfilter/nf_conntrack_count;
done

oom

  • /proc/$PID/oom_adj: 有效值是 -16 到 15,越大越容易被 kill。
  • /proc/$PID/oom_score: 综合进程的内存消耗量、CPU时间(utime + stime)、存活时间(uptime - start time)和oom_adj计算出的,消耗内存越多分越高,存活时间越长分越低
  • /proc/$PID/oom_score_adj: 有效值是 -1000 到 1000,Linux 2.6.36 之后用于替换 /proc/$PID/oom_adj

磁盘使用率

# df
Filesystem      1K-blocks       Used Available Use% Mounted on
/dev/vda1        20504188    4314736  15124852  23% /
tmpfs            16321844          0  16321844   0% /dev/shm
/dev/vdb       1548053708 1129305312 340088812  77% /data
# node
> 1129305312 / 1548053708
0.7295000852774031
> (1548053708 - 340088812) / 1548053708
0.7803120071077018
> 1548053708 - 1129305312 - 340088812
78659584
>

df 显示的 Used + Available != 1K-blocks(total),这是因为 ext2/3/4 文件系统默认保留 5% 的的可用空间给 root 用户,这样即使磁盘使用率达到 100%,仍有系统组件和操作管理所需的工作空间

使用 tune2fs -l /dev/vdb 可用查看 Reserved block count 和其他文件系统的信息。

使用 tune2fs -m <eserved-blocks-percentage> /dev/vdb 调整保留块的百分比。

如果要计算使用率,可以使用 (total - Available) / total