问题现象
•在某核心测试环境,进行压力测试;前置服务器通过网络发起压测后,交易达到核心系统(APP+DB)。此时,核心CPU运行100%,user 40%,kernel 60%左右,系统IO基本很小,网络流量也较小,此时在系统进行操作,并无卡顿现象,数据库也不繁忙无压力,连接数也较少。
•前端应用反应,核心交易超时,通道阻塞繁忙;压测时,系统topas如下:
问题分析
•客户在测试环境AIX系统上,/etc/security/limits设置了nofiles限制为-1, 即无限制;
default:
…
nofiles = -1
nofiles=-1这个设置会使得取单个进程所能够打开的最大文件描述符API操作(即getrlimit(RLIMIT_NOFILE, …) 操作)返回0x7FFFFFFF (最大的有符号整型,约21亿)。
•客户代码中,每次处理完一笔交易之后,会主动清理并关闭系统中所有可能存在的文件句柄;
−应用采取的方法是直接循环遍历从fd=5开始到fd=0x7FFFFFFF的全部文件句柄,然后close关闭之。
−实际上这些句柄并都不存在,所以这些close操作全部返回-1,errno设置为EBADF.
•因为每笔交易都涉及21亿次close关闭,因此造成了巨大的系统开销;
−巨量的close操作,导致kernel CPU使用率极高,每笔交易耗时极长,因此造成整体TPS极低。
系统工具truss追踪示例
•从truss可以很明显看到传递给close的文件句柄参数取值已经超过51273725,已经远远超出fd的合法取值范围。因此close操作总是返回EBADF错误。
•AIX环境即使设置了nofiles限制为unlimited,单个进程实际允许的最大打开文件数也不超过OPEN_MAX (65534);此外,句柄释放后会循环利用;因此实际open、socket等调用返回的文件句柄fd取值是不会超过65534的。
说明:
•fd取值范围:-1, 0~65533,-1为open错误时的返回值。
•fd关闭之后,其句柄会被循环利用。比如fd=10000的句柄,在close(10000)之后,就会进入空闲句柄池;下一次open 文件,返回的文件句柄就可能是10000。
解决方法
•可以使用如下任一方法来解决此问题:
•1.从系统层面限定nofiles为一个较小的整数;
−从实际情况观察看,进程实际同时打开的文件数不到10个,远低于2000;8000是相当安全的上限值。
−经测试,设置nofiles为8000之后,压测时系统CPU使用率正常,kernel CPU使用率降至个位数。
•2.修改应用程序代码,在应用中循环遍历close关闭文件句柄时,最大的fd不应该超过OPEN_MAX(可以使用getdtablesize API获取)或getrlimit(RLIMIT_NOFILE, …)中的较小者。这样应用程序的健壮性更高。
•3.最佳解决方法是调整业务逻辑,文件句柄释放通常的原则是谁申请则由谁释放。循环遍历所有可能存在的文件句柄来挨个close关闭是非常耗时的操作,不应该作为常规的清理手段。
−业务逻辑正常的情况下,不应该存在文件句柄泄露。如果业务能避免这些无必要的close操作(全部返回EBADF错误),相比于规避方案(每笔交易结束后,仍然需要执行数千至数万次无效close),应用运行时的系统CPU消耗还会进一步下降。