记一次线上崩溃问题的排查过程

开发 前端
由于正值双十一期间,业务以稳定为主,线上服务崩溃,这可不是一件小事,赶紧登陆线上服务器,分析原因,迅速解决。

 [[436425]]

大家好,我是雨乐!

前几天,突然收到报警,线上服务崩溃,然后自动重启。

由于正值双十一期间,业务以稳定为主,线上服务崩溃,这可不是一件小事,赶紧登陆线上服务器,分析原因,迅速解决。

借助这篇文章,记录下整个崩溃的分析和解决过程。

收到报警

上午上班后,正在划水,突然收到邮件报警,如下:

问题分析

马上登录线上服务器,gdb调试堆栈信息。

堆栈信息如下:

  1. #0  0x0000003ab9a324f5 in raise () from /lib64/libc.so.6 
  2. #1  0x0000003ab9a33cd5 in abort () from /lib64/libc.so.6 
  3. #2  0x0000003abcebea8d in __gnu_cxx::__verbose_terminate_handler() () from /usr/lib64/libstdc++.so.6 
  4. #3  0x0000003abcebcbe6 in ?? () from /usr/lib64/libstdc++.so.6 
  5. #4  0x0000003abcebcc13 in std::terminate() () from /usr/lib64/libstdc++.so.6 
  6. #5  0x0000003abcebcd32 in __cxa_throw () from /usr/lib64/libstdc++.so.6 
  7. #6  0x00000000006966bf in Json::throwRuntimeError(std::basic_string<char, std::char_traits<char>, std::allocator<char> > const&) () 
  8. #7  0x0000000000681019 in Json::Reader::readValue() () 
  9. #8  0x000000000068277c in Json::Reader::readArray(Json::Reader::Token&) () 
  10. #9  0x0000000000681152 in Json::Reader::readValue() () 
  11. #10 0x00000000006823a6 in Json::Reader::readObject(Json::Reader::Token&) () 
  12. #11 0x00000000006810f5 in Json::Reader::readValue() () 
  13. #12 0x0000000000680e6e in Json::Reader::parse(char const*, char const*, Json::Value&, bool) () 
  14. #13 0x0000000000680c52 in Json::Reader::parse(std::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, Json::Value&, bool) () 
  15. ...... 

在上面堆栈信息中可以看到在调用Json::Reader::parse后经过Json::Reader::readValue等调用,最后再调用Json::Reader::readValue时调用Json::throwRuntimeError抛出异常。

查看调用Json::throwRuntimeError函数的地方:

  1. src/lib_json/json_writer.cpp:    throwRuntimeError("commentStyle must be 'All' or 'None'"); 
  2. src/lib_json/json_reader.cpp:  if (stackDepth_g >= stackLimit_g) throwRuntimeError("Exceeded stackLimit in readValue()."); 
  3. src/lib_json/json_reader.cpp:  if (stackDepth_ >= features_.stackLimit_) throwRuntimeError("Exceeded stackLimit in readValue()."); 
  4. src/lib_json/json_reader.cpp:    if (name.length() >= (1U<<30)) throwRuntimeError("keylength >= 2^30"); 
  5. src/lib_json/json_reader.cpp:    throwRuntimeError(errs); 
  6. src/lib_json/json_value.cpp:    throwRuntimeError( 
  7. src/lib_json/json_value.cpp:    throwRuntimeError( 
  8. src/lib_json/json_value.cpp:JSONCPP_NORETURN void throwRuntimeError(JSONCPP_STRING const& msg) 
  9. src/lib_json/json_valueiterator.inl:  throwRuntimeError("ConstIterator to Iterator should never be allowed."); 

进入对应的函数

  1. bool Reader::readValue() { 
  2.   if (stackDepth_g >= stackLimit_g) throwRuntimeError("Exceeded stackLimit in readValue()."); 
  3.   ++stackDepth_g; 
  4.   ... ... 
  5.   --stackDepth_g; 
  6.   return successful; 

发现,在满足条件

  1. stackDepth_g >= stackLimit_g 

的时候,会调用throwRuntimeError,那么分析下stackDepth_g和stackLimit_g的声明定义:

  1. static int const stackLimit_g = 1000; 
  2.  
  3. static int stackDepth_g = 0; 

问题基本明了:

stackDepth_g是个静态全局变量,线程不安全,而出问题的服务是多线程的

在此准备吐槽下,笔者使用jsoncpp对象的时候,都是在线程内部一个局部变量,因此不会存在多线程访问同一个局部jsoncpp对象的时候,因此确定就是因为全局变量多线程访问导致的。一个开源的项目,里面竟然有全局变量,这在规范里面是不被允许的。

然后谷歌搜索了下大家都有过类似的问题,再次吐槽下。

问题解决

解决崩溃问题,首先需要看看是不是使用方式的问题,或者找一个线程安全的接口,再或者用其他库进行替换。

修改jsoncpp源码

为了解决线程安全的问题,有两种方案:1、在操作全局变量的时候,加上mutex,这个无非对性能要求很高的业务一个致命打击,为了提高业务性能,所以内部锁都使用其他方式进行了优化,比如mutex使用双buffer方式进行了替换,虽然mutex的一个加锁解锁过程也就100ns。

2、将上述全局变量放入Json对象中,这样局部变量就不会存在崩溃现象,但是这种方案存在一个问题,就是改动点很大,且需要大量严格的测试,放弃。

所以综合考虑上述两点,决定采用其他更安全可靠的方式来解决线上崩溃问题。

使用rapidjson

之所以采用rapidjson,是因为线上几十个服务,大部分都使用rapidjson,只有线上崩溃的这个服务等少数几个服务,因为历史原因,用的jsoncpp。

先介绍下rapidjson,下述内容来自于rapidjson官网:

  • RapidJSON 是一个 C++ 的 JSON 解析器及生成器。它的灵感来自 RapidXml。
  • RapidJSON 小而全。它同时支持 SAX 和 DOM 风格的 API。SAX 解析器只有约 500 行代码。
  • RapidJSON 快。它的性能可与 strlen() 相比。可支持 SSE2/SSE4.2 加速。
  • RapidJSON 独立。它不依赖于 BOOST 等外部库。它甚至不依赖于 STL。
  • RapidJSON 对内存友好。在大部分 32/64 位机器上,每个 JSON 值只占 16 字节(除字符串外)。它预设使用一个快速的内存分配器,令分析器可以紧凑地分配内存。
  • RapidJSON 对 Unicode 友好。它支持 UTF-8、UTF-16、UTF-32 (大端序/小端序),并内部支持这些编码的检测、校验及转码。例如,RapidJSON 可以在分析一个 UTF-8 文件至 DOM 时,把当中的 JSON 字符串转码至 UTF-16。它也支持代理对(surrogate pair)及 "\u0000"(空字符)。

不过rapidjson为了性能,在使用上面需要极其小心。

笔者之前踩过类似坑,局部字符串赋值给rapidjson对象,结果rapidjson并没有马上使用该局部字符串,而是在最后才会访问局部字符串里面的内容,而此时,局部字符串早已出了作用域,导致rapidjson获取的内容是乱码。

结语

在使用开源项目的时候,一定要做好调研,必要的时候,能过一下源码实现(这个有点难??),否则很容易入坑。

笔者在使用libcurl作为httpclient的时候,也因为触发了libcurl的一个bug,导致线上崩溃,当时连续通宵了两个晚上,才解决。

一入C++深似海,从此XX是路人。

责任编辑:武晓燕 来源: 高性能架构探索
相关推荐

2017-12-19 14:00:16

数据库MySQL死锁排查

2019-04-15 13:15:12

数据库MySQL死锁

2018-07-20 08:44:21

Redis内存排查

2023-01-04 18:32:31

线上服务代码

2023-04-06 07:53:56

Redis连接问题K8s

2021-05-13 08:51:20

GC问题排查

2018-11-13 10:44:23

Linux服务器双网卡网络

2020-11-16 07:19:17

线上函数性能

2021-03-29 12:35:04

Kubernetes环境TCP

2019-09-10 10:31:10

JVM排查解决

2019-06-10 15:20:18

2021-05-31 10:08:44

工具脚本主机

2019-03-15 16:20:45

MySQL死锁排查命令

2022-02-08 17:17:27

内存泄漏排查

2022-07-13 08:31:18

React问题排查

2021-03-05 07:14:08

Linuxcrashvmcore

2024-10-15 09:27:36

2024-04-10 08:48:31

MySQLSQL语句

2019-08-26 09:50:09

2023-10-10 12:05:45

点赞
收藏

51CTO技术栈公众号