百亿级金融应用高并发导致的文件操作问题

一、为什么写这样一篇文章

最近团队都在处理因高并发导致各种问题:

历史遗留项目依赖jar过多,在启动时出现too open files异常;

业务高峰时应用无法打开更多连接而抛异常

用户头像服务报无法上传异常

Nginx静态资源访问量大时报too open files;

概括起来都是因为以下两个原因一起的:

打开文件过多

打开网络连接过多

二、为什么发生此问题?

当高并发情况下,用户请求到达服务器之后,Java 应用会打开许多文件,以便读取运行应用程序所必需的类。大量应用程序会使用许多文件描述符,这会导致缺乏新的文件描述符而抛异常。

同样,每个客户端和服务器通过 TCP 套接字连接进行通信,每个TCP套接字连接也都需要一个文件描述符,大量的连接建立也会导致缺乏新的文件描述符而抛异常。

如果你是一位老司机,在高并发访问文件系统,多线程网络连接等场景,常会遇到以下两个异常。

异常一:在高并发 I/O 操作或文件泄露时抛出:

异常二:在高并发TCP请求或网络连接泄露时抛出。

这两个异常都是由超过系统文件描述符限制所产生的,这两个异常明确指出了操作系统 (OS) 资源设置问题或JVM 进程限制的问题(直白一点就是文件描述符数过多超过系统或JVM限制无法新建了),当然还有间接导致的的问题,这给我们生产问题排查,增加了不少难度。

这里两个问题都涉及到了文件描述符,那么什么是文件描述符,为什么需要它呢?

三、深入了解文件描述符

在Linux系统中一切皆可以看成是文件,设备是文件,目录是文件,socket 也是文件,用来表示所处理对象的接口和唯一接口就是文件。

应用程序在读 / 写一个文件时,首先需要打开这个文件,打开的过程其实质就是在进程与文件之间建立起连接,文件描述符的作用就是唯一标识此连接。最后关闭文件其实就是释放这个文件描述符的过程,使得进程与文件之间的连接断开。

要了解 Linux 当中的文件描述符,必须先了解 Linux 的文件系统,下图表示了 Linux 的每个进程 (task) 的结构,及 task 所打开的文件的结构。

Linux 用struct task来表示进程/线程,用struct file表示打开的文件,用struct inode表示文件本身。struct file 和 struct inode 的区别在于,如果两次 open 同一个文件(比方说 web server 写 access log,你用 less 看这个 assess log 文件),会有两个 struct file 对象,指向同一个 struct inode 对象。

四、典型案例解析

4.1 Dubbo服务Too many open files问题分析

某日生产服务器发生预警,某某查询服务器大量连接失败,通过CAT监控查看日志发现为Too many open files异常。

奇怪的是只有该服务器出现异常,其它服务器都是正常的,通过netstat -an|grep 28503|wc -l查看服务器的连接数只有238个。与其他服务器相当。

进一步通过#ps –ef | grep java 查看进程信息

dubbo71301 99 22:40 pts/4 00:00:16 /usr/java/jdk1.8.0_74/bin/java -Djava.util.logging.config.file=/home/dubbo/tools/dubbo

# cat /proc/7130/limits 查看该进程打开最大文件数设置为4096

通过ulimit -n和ulimit -a查看操作系统限制最大打开文件数为1024

通过以上对比,发现是在上线时系统优化参数设置遗漏导致的问题。通过修改相关系统参数重启dubbo服务后问题得到临时解决(具体修改参数见后面调优部分介绍)。

其实到这里还没有结束,这个dubbo服务只提供简单的数据查询服务,响应延时低且无积压请求,不应该出现Too many open files问题才对,后来排查发现存在连接泄漏,因为该主题网上已经有人做了详细描述,本文不详细描述。可以参见《侦测程序句柄泄露的统计方法》:https://www.ibm.com/developerworks/cn/linux/l-cn-handle/了解分析过程。

通过这个案例我想说的是大家在百度上看到的类似问题建议直接修改ulimt -n参数是不全面的,要透过现象多问题几个问题,确保真正的解决了问题。

4.2由Too many open files异常间接引发的问题

通常间接异常难于发现根本原因,下面是我为了复现该问题通过ulimit -n 10手动将最大打开文件数设置为10,引起tomcat启动异常的案例:

从异常堆栈中我们看到了java.io.FileNotFoundException: /home/dubbo/tools/dubbo-admin-tomcat/logs/host-manager.2018-06-03.log (Too many open files)异常(这个是超过了系统或jvm允许的最大打开文件数所致),也看到了java.lang.ClassNotFoundException: org.apache.catalina.startup.Catalina(这个要特别注意,有的时候你应用里明明存在需要的类,怎么还会抛出ClassNotFoundException异常,查看ClassNotFoundException.java源码,你会发现有这么一段描述:Thrown when an application tries to load in a class through ,也就是说Classloader没有正常加载,这个和类存在与否不可等同)

4.3 应用操作规范

开发和部署时应该遵循一些规范,以规避相关问题:

进程在调用系统文件之后,要及时关闭已经打开的文件。

系统资源是有限的需要做好容量估算,如linux接受端口和发送端口最大分别为65535个,根据应用和CPU等资源限制,需要依据监控及时横向扩展。

linux系统ext3文件系统的Inodes个数也是有限制的这个和挂在目录大小等有关,在进行大量文件存储时注意做好容量估算。

ext3文件系统一级子目录的个数默认最大为31998个,所以在进行文件保存时要分级目录保存,建议按照业务类型/年/月/日/时/分/秒来保存,当然如果平均每秒新建目录数不超过9个也可以到分钟级别。

生产环境部署要好定期的巡检,检查内容包括日志、环境参数设置等。

五、性能调优

5.1 查看系统级允许的最大打开文件数

系统级别的最大同时打开的文件数目,可以使用sysctl -a | grep fs.file-max命令查看:

这个值建议修改为fs.file-max = 6815744,其值相当于 6.5×1024×1024=6.5M

修改文件:/etc/sysctl.conf。在文件中添加:fs.file-max=655350

运行命令:/sbin/sysctl -p 使配置生效

5.2 查看用户级允许的最大打开文件数

因为内核为了不让某一个进程消耗掉所有的文件资源,其也会对单个进程最大打开文件数做默认值处理(称之为用户级限制),默认值一般是1024,使用ulimit -n命令可以查看。

过去是因为服务器紧张,所以单服务器复用,现在为了管理方便和避免复杂度,服务器已经专用和定制化,该参数可以根据实际需要调整,建议修改为40960或者65535。

修改文件:/etc/security/limits.conf

* soft nofile 40960#限制单个进程最大文件句柄数(到达此限制时系统报警)

* hard nofile 65536 #限制单个进程最大文件句柄数(到达此限制时系统报错)

六、小结

本文讲了文件操作的概念原理、常见问题及规范,希望通过本文的总结,可以掌握类似问题生产查找方法、熟悉Linux底层机制为进一步系统调优奠定基础,如果你们项目即将上线,那么希望本文的的相关建议可以使你规避类似问题。

本文作为生产问题处理系列的第四篇,前面几篇为:

Java应用CPU占用率典型案例解析

《百亿规模金融系统-内存泄露实战》

《百亿级金融架构实践:生产问题诊断利器》。

下一篇我们讲java应用异常挂起的案例解析,请关注公众号订阅消息。

END

  • 发表于:
  • 原文链接https://kuaibao.qq.com/s/20180604G072EF00?refer=cp_1026
  • 腾讯「云+社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。

扫码关注云+社区

领取腾讯云代金券

年度创作总结 领取年终奖励