背景

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

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

分析过程

蛛丝马迹

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

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

日志迷云

这就奇怪了,因为我们的数据流都是走page cache,而且落盘的时候用pthread, 即便磁盘坏掉了,也只应该影响这个pthread, 不应该导致整个存储进程上所有其他的线程,持续周期任务日志都打印不出来的情况。

到底是个什么鬼?

好事多磨

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

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

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

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

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

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

直捣长龙 (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住无法返回,就会导致存储进程假死!

总结

通过上面的分享可以看到,分析和模拟磁盘故障,需要知道它的本质影响然后想办法模拟手影响的地方,这样才能彻底定位根因!