<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Work on codedump notes</title>
    <link>https://www.codedump.info/zh/tags/work/</link>
    <description>Recent content in Work on codedump notes</description>
    <generator>Hugo</generator>
    <language>zh</language>
    <lastBuildDate>Sat, 13 Apr 2019 12:04:59 +0800</lastBuildDate>
    <atom:link href="https://www.codedump.info/zh/tags/work/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>线上存储服务崩溃问题分析记录</title>
      <link>https://www.codedump.info/zh/post/20190413-problem-fix/</link>
      <pubDate>Sat, 13 Apr 2019 12:04:59 +0800</pubDate>
      <guid>https://www.codedump.info/zh/post/20190413-problem-fix/</guid>
      <description>&lt;p&gt;注：本文为重新发布2017-06-17所写博客，以下为正文部分。&lt;/p&gt;&#xA;&lt;p&gt;上周我们的存储服务在某个线上项目频繁出现崩溃，花了几天的时间来查找解决该问题。在这里，将这个过程做一下记录。&lt;/p&gt;&#xA;&lt;h1 class=&#34;heading&#34; id=&#34;加入调试信息&#34;&gt;&#xA;  加入调试信息&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e5%8a%a0%e5%85%a5%e8%b0%83%e8%af%95%e4%bf%a1%e6%81%af&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h1&gt;&#xA;&lt;p&gt;由于问题在线上发生，较难重现，首先想到的是能不能加上更多的信息，在问题出现时提供更多的解决思路。&lt;/p&gt;&#xA;&lt;p&gt;首先，我们的代码里，在捕获到进程退出的信号比如SIGABRT、SIGSEGV、SIGILL等信号时，会打印出主线程的堆栈，用于帮助我们发现问题。&lt;/p&gt;&#xA;&lt;p&gt;但是在崩溃的几次情况中，打印出来的信息并不足以帮助我们解决问题，因为打印的崩溃堆栈只有主线程，猜测是不是在辅助线程中发生的异常，于是采取了两个策略：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;ulimit命令打开线上一台服务器的coredump，当再次有崩溃发生时有core文件产生，能够帮助发现问题。&lt;/li&gt;&#xA;&lt;li&gt;加入了一些代码，用于在崩溃的时候同时也打印出所有辅助线程的堆栈信息。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;在做这两部分工作之后，再次发生崩溃的情况下，辅助线程的堆栈并无异常，core文件由于数据错乱也看不出来啥有用的信息来。&lt;/p&gt;&#xA;&lt;h1 class=&#34;heading&#34; id=&#34;复现问题&#34;&gt;&#xA;  复现问题&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e5%a4%8d%e7%8e%b0%e9%97%ae%e9%a2%98&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h1&gt;&#xA;&lt;p&gt;由于第一步工作受挫，接下来我的思路就在考虑怎么能在开发环境下复现这个问题。&lt;/p&gt;&#xA;&lt;p&gt;我们的存储服务在其他项目上已经上线了有一段时间了，但是并没有出现类似的问题。那么，出现问题的项目，与其他已经上线的服务有啥不同，这里也许是一个突破口。&lt;/p&gt;&#xA;&lt;p&gt;经过咨询业务方，该业务的特点是：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;单条数据大：有的数据可能有几KB，而之前的项目都只有几百字节。&lt;/li&gt;&#xA;&lt;li&gt;读请求并发大，而其他业务是写请求远大于读请求。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;由于我们的存储服务兼容memcached协议，出现问题时也是以memcached协议进行访问的，所以此时我的考虑是找一个memcached压测工具，模拟前面的数据和请求特点来做模拟压测。&lt;/p&gt;&#xA;&lt;p&gt;最后选择的是twitter出品的工具twemperf，其特点是可以指定写入缓存的数据范围，同时还可以指定请求的频率。&lt;/p&gt;&#xA;&lt;p&gt;有了这个工具，首先尝试了往存储中写入大量数据量分布在4KB~10KB的数据，此时没有发现服务有core的情况出现。&#xA;然后，尝试构造大量的读请求，果然出现了core情况，重试了几次，都能稳定的重现问题了。&lt;/p&gt;&#xA;&lt;p&gt;有了能稳定重现问题的办法，总算给问题的解决打开了一个口子。&lt;/p&gt;&#xA;&lt;h1 class=&#34;heading&#34; id=&#34;首次尝试&#34;&gt;&#xA;  首次尝试&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e9%a6%96%e6%ac%a1%e5%b0%9d%e8%af%95&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h1&gt;&#xA;&lt;p&gt;此时，可以正式的在代码中查找问题的原因了。&lt;/p&gt;&#xA;&lt;p&gt;来大概说明一下该存储服务的架构：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;主线程负责接收客户端请求，并且进行解析。&lt;/li&gt;&#xA;&lt;li&gt;如果是读请求，将分派给读请求处理线程，由这个线程与存储引擎库进行交互，查询数据。此时该线程数量配置为2。&lt;/li&gt;&#xA;&lt;li&gt;存储引擎库负责存储落地到磁盘的数据，类似leveldb，只不过这部分是我们自己写的存储引擎。&lt;/li&gt;&#xA;&lt;li&gt;在读线程从存储引擎中查询数据返回后，将把数据返回给主线程，由主线程负责应答客户端。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&lt;figure class=&#34;&#34;&gt;&#xA;&#xA;    &lt;div&gt;&#xA;        &lt;img loading=&#34;lazy&#34; alt=&#34;server&#34; src=&#34;https://www.codedump.info/media/imgs/20190413-problem-fix/server.png&#34; &gt;&#xA;    &lt;/div&gt;&#xA;&#xA;    &#xA;    &lt;div class=&#34;caption-container&#34;&gt;&#xA;        &lt;figcaption&gt; server &lt;/figcaption&gt;&#xA;    &lt;/div&gt;&#xA;    &#xA;&lt;/figure&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;在这几步中，第1和第4步是在主线程中进行的，第2和第3步是在读存储引擎线程中进行的。在这个过程中，如果同一个客户端有多个读请求，那么只有按照这四步在处理完毕一个读请求之后，才会继续从该客户端中取出下一个请求进行处理。&lt;/p&gt;&#xA;&lt;p&gt;在几次重现问题的过程中，发现出错的都是在第2步和第4步中，该请求客户端的数据结构某些成员出现了错乱，即要访问的指针地址已经无效了，导致的错误。&lt;/p&gt;&#xA;&lt;p&gt;指针无效，一般来说有两种可能性：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;被无效地址覆盖了这个指针。&lt;/li&gt;&#xA;&lt;li&gt;指针已经被释放的情况下继续使用。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;当时尝试把一些错误的指针地址打印出来，发现有几次都是是字符串“pcm*”的16进制表示，当时在想这个特殊的字符串到底是什么，百思不得其解的时候，一位曾经使用过mcperf工具的同事，想起来mcperf做压测时的key就是&amp;quot;mcp&amp;quot;开头的，而因为是小端方式，所以如果使用这个类型的字符串，去覆盖指针，那么就变成了&amp;quot;pcm&amp;quot;。我们很快验证了这个说法，mcperf确实是以这个为前缀来写入数据的。&lt;/p&gt;&#xA;&lt;p&gt;此时，猜测问题的原因在于：当读存储引擎线程去访问存储引擎时，某些错误导致从存储引擎读出来的数据，将客户端请求数据写乱，从而导致了崩溃。&lt;/p&gt;&#xA;&lt;p&gt;由于同时有两个读存储引擎的线程，猜测这里是不是因为多线程访问出了问题，导致的错误呢？&lt;/p&gt;&#xA;&lt;p&gt;为了验证这个问题，最简单的办法就是将线程数量改成1，重新用mcperf试了几次，确实没有再次出现问题。此时已经是周五，我们缓了一口气，打算以此修改暂时上测试环境利用周末的时间观察一下情况。&lt;/p&gt;&#xA;&lt;h1 class=&#34;heading&#34; id=&#34;柳暗花明&#34;&gt;&#xA;  柳暗花明&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e6%9f%b3%e6%9a%97%e8%8a%b1%e6%98%8e&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h1&gt;&#xA;&lt;p&gt;前面提到过，猜测问题出现的原因，是多线程访问存储引擎时将某个数据写错乱了，导致其中的指针无效。&lt;/p&gt;&#xA;&lt;p&gt;clang和gcc 4.8有对应的编译参数，可以用来检测内存错误的写操作，即Address Sanitizer工具。为了兼容线上比较老的系统，之前我们的服务都是在gcc 4.1的环境下进行编译的，为了使用这个工具，首先需要折腾到满足gcc版本号大于4.8的系统上进行编译。&lt;/p&gt;&#xA;&lt;p&gt;然而，在折腾编译并且运行后，同样使用mcperf的情况下，并不能看到有内存错误覆盖写的提示，我尝试了多次都没有看到。难道是工具没有起作用？&lt;/p&gt;&#xA;&lt;p&gt;为了验证该工具的作用，我简单在出错代码的前面加入了一段肯定有问题的代码，比如：&lt;/p&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;char a[100] = {&amp;#39;0&amp;#39;};&#xA;a[100] = &amp;#39;1&amp;#39;;&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;而在加入这段有问题的代码之后再次运行，就能看到编译器对这段代码的提示。可见，Address Sanitizer工具是起作用的。那么，前面的过程中没有看到问题，只能说明一个问题：并没有内存错误写的情况发生。&lt;/p&gt;&#xA;&lt;p&gt;此时想到另一个可能，就是有没有可能是多线程在没有保护的情况下访问了某段数据导致的问题？&lt;/p&gt;&#xA;&lt;p&gt;gcc同样也有类似的工具来检查这类错误，即Thread Sanitizer工具。然而，在给项目Makefile加入该编译参数后，程序一运行就退出了，根本看不出什么有用的信息来。&lt;/p&gt;&#xA;&lt;p&gt;此时想到的另一个工具是valgrind。大多数时候，valgrind只是用来做内存泄露检测的，其实它也可以用来做线程数据竞争的检查，使用参数 &amp;ndash;tool=helgrind 即可。使用valgrind之后，打印出来疑似有问题的代码如下：&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&lt;figure class=&#34;&#34;&gt;&#xA;&#xA;    &lt;div&gt;&#xA;        &lt;img loading=&#34;lazy&#34; alt=&#34;valgrind&#34; src=&#34;https://www.codedump.info/media/imgs/20190413-problem-fix/valgrind.png&#34; &gt;&#xA;    &lt;/div&gt;&#xA;&#xA;    &#xA;    &lt;div class=&#34;caption-container&#34;&gt;&#xA;        &lt;figcaption&gt; valgrind &lt;/figcaption&gt;&#xA;    &lt;/div&gt;&#xA;    &#xA;&lt;/figure&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;到了这里，猜测问题的原因就是由于多线程访问数据导致的错误。&lt;/p&gt;&#xA;&lt;p&gt;因为有多个处理读请求数据的线程，首先猜测的是不是某些错误的处理，导致了可以在同一时间多个线程都操作该请求客户端的数据。但是通过review代码，发现这部分处理是没有问题的，另外在访问存储引擎查询数据时，入口处也确实进行了加锁的操作。&lt;/p&gt;</description>
    </item>
  </channel>
</rss>
