句柄监控优化

脚本

1
lsof -n | awk '{print $2"_"$3}'|sort|uniq -c > pid_fds 

ps aux | grep "\-Dcatalina.home=/home\|ACE\-uWSG\|php\-fpm"|grep -v "grep" | awk '{print $2"_"$1}' > pids

for pid in `cat pids`
do
  echo `grep "$pid"  `
done

这是VM虚拟机上句柄监控的采集脚本,主要目的是统计以下三个进程打开的句柄数量

  • -Dcatalina.home=/home
  • ACE-uWSG
  • php-fpm

线上进行链路压测时发现,这段脚本的执行会导致CPU负载飙升。

lsof

lsof 是一个很常用的问题排查工具,它能列出系统中打开的文件句柄。

Lsof revision 4.78 lists information about files opened by processes.

An open file may be a regular file, a directory, a block special file, a character special file, an executing text reference, a library, a stream or a network file (Internet socket, NFS file or UNIX domain socket.)

这是man手册中对lsof的描述,文件句柄可能包括:

  • 普通文件
  • 目录
  • 块文件(块设备)
  • 字符文件(字符设备)
  • 正在执行中的文件的引用
  • socket和NFS

linux中一切都是文件 : )
我们可以通过指定参数,展示某些具体进程或者目录下打开的句柄(-p +d),具体使用方法参考这篇文档,不再赘述。15 Linux lsof Command Examples

工作方式

Lsof obtains data about open UNIX dialect files by reading the kernel’s proc structure information, following it to the related user structure, then reading the open file structures stored (usually) in the user structure. Typically lsof uses the kernel memory devices, /dev/kmem, /dev/mem, etc. to read kernel data.

from–Guide to Porting lsof 4 to Unix OS Dialects

根据参考资料,lsof 通过读取内核中的 proc 数据结构,并根据他们去获取用户的相关信息,然后读取存储在用户数据结构中的句柄信息。
进程相关data_structure

实际上句柄就是上图中fd_array的 下标 ,如果某个进程打开了某个”文件”,对应的数组元素就会指向其文件的元信息。

优化方案

师兄给出了两个他曾经考虑过的方案

  • lsof -p pid
  • ls /proc/pid/fd
1
lsof -p pid | wc -l

--------------------------

ls -l /proc/pid/fd | wc -l

结果 497 : 298

然而问题发生了,查看同一个进程打开的句柄,两种方案给出的执行结果却不一样。lsof命令的执行结果比另一个方案,多出好几百条句柄。

我试着把两种方案的结果dump到本地

1
lsof -n -p 165926 | awk '{if($5=="REG") print $9"_"$8"_"$4;else print $8"_"$7"_"$4}' | sort > fd_lsof

ls -il /proc/165926/fd |grep -v "total"| awk '{print $12"_"$1}' | sort > fd_proc

Beyond Compare 进行比对,发现了以下情况

左边是proc/pid/fd的结果,右边是lsof命令的结果

  1. lsof除了列出该进程对所有jar包的访问而产生的句柄外,还列出了同一个jar包作为共享库的句柄。(我将句柄类型输出到了右边的末尾,mem就表示内存映射的句柄)
  2. 除此之外,lsof还列出了以 .so 结尾的库文件

库文件以内存映射方式加载到内存中,lsof将其又统计了一遍,导致结果数量和proc方式不一致。
并且很有意思的是那些mem类型的输出,他们inode的指向和对应句柄的inode指向是一致的,也就是说,他们本质上打开的是同一个文件。
因此,将mem类型的输出过滤掉就ok了。

参考资料

  1. 15 Linux lsof Command Examples
  2. how lsof works