Archive for 六月 2017

线上存储服务崩溃问题分析记录

上周我们的存储服务在某个线上项目频繁出现崩溃,花了几天的时间来查找解决该问题。在这里,将这个过程做一下记录。

加入调试信息

由于问题在线上发生,较难重现,首先想到的是能不能加上更多的信息,在问题出现时提供更多的解决思路。

首先,我们的代码里,在捕获到进程退出的信号比如SIGABRT、SIGSEGV、SIGILL等信号时,会打印出主线程的堆栈,用于帮助我们发现问题。

但是在崩溃的几次情况中,打印出来的信息并不足以帮助我们解决问题,因为打印的崩溃堆栈只有主线程,猜测是不是在辅助线程中发生的异常,于是采取了两个策略:

  1. ulimit命令打开线上一台服务器的coredump,当再次有崩溃发生时有core文件产生,能够帮助发现问题。
  2. 加入了一些代码,用于在崩溃的时候同时也打印出所有辅助线程的堆栈信息。

在做这两部分工作之后,再次发生崩溃的情况下,辅助线程的堆栈并无异常,core文件由于数据错乱也看不出来啥有用的信息来。

复现问题

由于第一步工作受挫,接下来我的思路就在考虑怎么能在开发环境下复现这个问题。

我们的存储服务在其他项目上已经上线了有一段时间了,但是并没有出现类似的问题。那么,出现问题的项目,与其他已经上线的服务有啥不同,这里也许是一个突破口。

经过咨询业务方,该业务的特点是:

  • 单条数据大:有的数据可能有几KB,而之前的项目都只有几百字节。
  • 读请求并发大,而其他业务是写请求远大于读请求。

由于我们的存储服务兼容memcached协议,出现问题时也是以memcached协议进行访问的,所以此时我的考虑是找一个memcached压测工具,模拟前面的数据和请求特点来做模拟压测。

最后选择的是twitter出品的工具twemperf,其特点是可以指定写入缓存的数据范围,同时还可以指定请求的频率。

有了这个工具,首先尝试了往存储中写入大量数据量分布在4KB~10KB的数据,此时没有发现服务有core的情况出现。

然后,尝试构造大量的读请求,果然出现了core情况,重试了几次,都能稳定的重现问题了。

有了能稳定重现问题的办法,总算给问题的解决打开了一个口子。

首次尝试

此时,可以正式的在代码中查找问题的原因了。

来大概说明一下该存储服务的架构:

  1. 主线程负责接收客户端请求,并且进行解析。
  2. 如果是读请求,将分派给读请求处理线程,由这个线程与存储引擎库进行交互,查询数据。此时该线程数量配置为2。
  3. 存储引擎库负责存储落地到磁盘的数据,类似leveldb,只不过这部分是我们自己写的存储引擎。
  4. 在读线程从存储引擎中查询数据返回后,将把数据返回给主线程,由主线程负责应答客户端。