Log把磁盘写满了会有什么后果
背景
最近在测试环境灰度时,业务反应服务无响应,然后马上摘除节点恢复业务。
到测试机器检查,发现进程存在,日志正常。
printf "stats\r\n"|nc localhost 10101
测试端口发现命令卡住,大约10s才回复。
syscall:write耗时
df -h发现磁盘写满,然而磁盘满会使写磁盘这么慢吗?
用strace查看每次write耗时

每次write耗时250ms+...有点长。
修改代码,把_log函数体注释,重新编译发现服务恢复正常。
在印象中,感觉磁盘满不可能会导致写文件这么慢啊,有啥问题呢?
又找了一台新的机器,写了小程序先把磁盘写满,然后启动同样的程序,测试发现一切正常

这就很有趣了,那么问题在哪呢?
ext4
uname -r发现没有问题的内核版本是3.10.0-229.el7.x86_64,有问题的版本是2.6.32-431.11.2.el6.toa.2.x86_64
df -T发现文件系统都是ext4,怀疑可能是ext4文件系统某些机制导致的
使用perf stat查看所有ext4文件系统的trace event
sudo perf stat -e "ext4:*" -p 23488 sleep 10
有问题的机器

img 没问题的机器

img
发现ext4的执行路径差异很大,然后同时trace ext4和syscall:write
sudo perf stat -e "syscalls:sys_enter_write,ext4:*" -p 23488 sleep 10

试了几次发现syscall:write和ext4:ext4_da_write_begin次数完全一样,这说明每次write都会调用ext4的ext4_da_write_begin,但是接下来的执行逻辑就不一样了。
内核源码
翻内核的源码发现,函数ext4_da_write_begin在3.10和2.6差别很多,用systemtap脚本调试N久,尝试复原ext4_da_write_begin不同的路径
看到在写满ENOSPC的错误发生时,ext4_should_retry_alloc函数的嫌疑很大,怀疑是触发了不同的逻辑导致低版本内核频繁的重试
用脚本trace发现在函数ext4_should_retry_alloc里有差异,有问题的机器会去频繁jbd2_journal_force_commit_nested,而没问题的机器直接return 0
stap脚本
有问题的机器

img 没问题的机器

img
继续往下看
发现在函数ext4_has_free_blocks中会判断是否会有空闲块,决定是不是重试,对比3.10的代码,在3.10中的函数是
发现在判断逻辑上的不同,在3.10里,函数ext4_has_free_clusters调用时,flags传入0,所以不考虑root用户的预留空间,而在2.6里会判断用户是否是root用户,假如是root用户,那么在判断空闲块是否够用时会加上root用户的预留空间。
写个脚本看一下free_blocks,nblocks,dirty_blocks,root_blocks分别是多少

root用户的判断逻辑是free > nblocks + dirty,是true,所以一直在重试
非root用户的判断逻辑是free > nblocks + dirty + root,是false,所以不会重试
验证
这应该是问题所在了,那么也很好验证,在低内核版本上只有root用户才有权限使用预留空间,那么我们用其他用户启动程序,应该就没有这个问题了。

重新用非root用户启动程序,strace查看,完全正常。
总结
那么这个问题就是由于低版本的内核文件系统某些缺陷导致的,但是线上的内核版本不一,假如由于某些问题或者其他进程把磁盘刷爆,那么很有可能导致服务不可用。
预防措施:
对日志增加监控,防止出现日志把磁盘刷爆的情况
尽量用非root用户启动进程
最后更新于