一次线上存储节点进程假死的分析

背景

某大厂分布式存储程序猿大半夜收到电话报警,某个存储节点进程和主控节点心跳丢了。
穿着小内裤起来 检查了一番:网络

  • ping 主控节点:网络正常
  • telnet 主控节点端口: 能telnet 上
  • ps -aux 检查存储节点进程: 进程还在
  • 检查 日志: 日志竟然不前进了!!!!
    又看了一遍 存储节点主进程的状态:竟然不是正常状态!!!
    根据线上故障先止损后追因的原则,立刻发命令check 无误后把这个节点从存储集群中下掉了。

分析过程

蛛丝马迹

看到业务恢复、故障报警消除后,开始分析root cause。
进程处于func状态, gdb attach 上去失败, gcore 也失败,根本就没法看到堆栈。
继续检查业务日志:发现故障在磁盘包障后的日志后出现。
检查内核日志:发现有块nvme 盘掉线了,和pcie root 断开了!!!分布式

和厂商及系统部同窗确认了下,出现NVME盘掉线的状况,短时不影响buffer IO ,只影响direct IO。ide

日志迷云

这就奇怪了,由于咱们的数据流都是走page cache,并且落盘的时候用pthread, 即使磁盘坏掉了,也只应该影响这个pthread, 不该该致使整个存储进程上全部其余的线程,持续周期任务日志都打印不出来的状况。工具

究竟是个什么鬼?测试

好事多磨

现有的日志和现场已经没法提供更多线索了,为了分析这个问题,只有想办法复现这个现象了。线程

怎么复现了?既然是坏盘影响IO,那么我就模拟坏盘吧。
搜索了一卷: 参考 磁盘故障模拟, 发现验证机器上没有相关的工具,也没法安装包,之好做罢。怎么办?日志

喝了一杯茶回来,想到既然是坏盘影响IO, 那么就把有IO的地方mock住!code

说干就干,把raft 中和业务中数据流上pwrite/pread的地方都替换成无限sleep, 加个开关,启动后验证了一遍,一切正常,没有复现哪一个问题。进程

后来仔细分析了下,不能一直sleep后,应该用while(1)啊,由于碰到磁盘故障,DIO执行到这里也不会返回,而且不会切换线程。 因而把上面的 sleep换成while。ci

从新编译执行,打开开关测试 ,仍是一切正常啊!

直捣长龙 (pwrite --> sync)

又分析了一遍,既然NVME盘掉线,直接影响的是DIO,那么mock的时候:全部相关的这类操做都应该换成while(1)没法返回,DIO的操做有哪些?

write(O_DIRECT);
sync

grep 了一遍,没有O_DIRECT, 只有sync有几个,也换成了while(1), 从新启动,先正常跑了一通,而后把替换sync为 while(1)的开关打开,没过几分钟,就出现了和线上如出一辙的假死现象。

根因

仔细分析了下咱们的业务,原来在每次元数据更新完都会sync下元数据文件。这个操做在bthread里,为了不同时操做这个文件,前面有锁。而这个操做又是在一个bthread里, 它作这个又会去拿磁盘基本的锁,这个锁又和不少业务线程互斥!!!

这样一旦这个sync huang住没法返回,就会致使存储进程假死!

总结

经过上面的分享能够看到,分析和模拟磁盘故障,须要知道它的本质影响而后想办法模拟手影响的地方,这样才能完全定位根因!