当前位置: 首页 > 科技观察

记一个在线崩溃排查流程

时间:2023-03-18 16:33:31 科技观察

大家好,我是玉乐!前几天突然接到报警,在线服务崩溃,然后自动重启。由于双十一期间,业务以稳定为主,线上服务崩溃。这不是一件小事。赶紧登录在线服务器,分析原因,赶紧解决。借助本文记录下整个crash的分析和解决过程。接到报警后,早上上班后,正划水,突然收到一封邮件报警,如下:问题分析立即登录在线服务器,gdb调试堆栈信息。堆垛信息如下:#00x0000003ab9a324f5inraise()from/lib64/libc.so.6#10x0000003ab9a33cd5inabort()from/lib64/libc.so.6#20x0000003abcebea8din__gnu_cxx::__verbose_terminate_libst.libst.libr++c()from/us.#30x0000003abcebcbe6in??()来自/usr/lib64/libstdc++.so.6#40x0000003abcebcc13instd::terminate()()来自/usr/lib64/libstdc++.so.6#50x0000003abcebcd32in__cxa_throw()来自/usr/lib64/libstdc++.so.6#60x00000000006966bfinJson::throwRuntimeError(std::basic_string,std::allocator>const&)()#70x00000000000681019inJson::Reader::readValue()()#80x0000000000Json:2777:Reader::readArray(Json::Reader::Token&)()#90x0000000000681152inJson::Reader::readValue()()#100x00000000006823a6inJson::Reader::readObject(Json::Reader::Token&)()#110x00000000006810f5inJson::Reader::readValue()()#120x0000000000680e6einJson::Reader::parse(charconst*,charconst*,Json::Value&,bool)()#130x00000000000680c52inJson::Reader::parse(std::basic_string,std::allocator>const&,Json::Value&,bool)()...在调用Json之后可以看到上面的栈信息::Reader::parse、Json::Reader::readValue等调用,最后在调用Json::Reader::readValue时调用Json::throwRuntimeError抛出异常查看调用Json::throwRuntimeError函数的地方:src/lib_json/json_writer.cpp:throwRuntimeError("commentStylemustbe'All'or'None'");src/lib_json/json_reader.cpp:if(stackDepth_g>=stackLimit_g)throwRuntimeError("ExceededstackLimitinreadValue().");src/lib_json/json_reader.cpp:if(stackDepth_>=features_.stackLimit_)throwRuntimeError("ExceededstackLimitinreadValue().");src/lib_json/json_reader.cpp:if(name.length()>=(1U<<30))throwRuntimeError("keylength>=2^??30");src/lib_json/json_reader.cpp:throwRuntimeError(errs);src/lib_json/json_value.cpp:throwRuntimeError(src/lib_json/json_value.cpp:throwRuntimeError(src/lib_json/json_value.cpp:JSONCPP_NORETURNvoidthrowRuntimeError(JSONCPP_STRINGconst&msg)src/lib_json/json_valueiterator.inl:throwRuntimeError("ConstIteratortoIteratorshouldneverbeallowed.");进入对应的函数boolReader::readValue(){if(stackDepth_g>=stackLimit_g)throwRuntimeError("超过stackLimitinreadValue().");++stackDepth_g;......--stackDepth_g;returnsuccessful;}发现当满足stackDepth_g>=stackLimit_g条件时,会调用throwRuntimeError,接着分析stackDepth_g的声明定义andstackLimit_g:staticintconststackLimit_g=1000;staticintstackDepth_g=0;问题基本清楚了:stackDepth_g是静态全局变量,线程不安全,问题服务是多线程的,在这个编写中,当作者使用jsoncpp对象,都是在一个线程内部的局部变量,所以不会出现多线程访问同一个局部jsoncpp对象,所以确定是多线程访问全局变量导致的一个开源项目里面有全局变量,规范上是不允许的,然后谷歌一搜大家都有类似的问题,又投诉了。解决闪退的问题,首先要检查是不是使用方式的问题,或者找一个thread-safe接口,或者换成其他库。修改jsoncpp源码,解决线程安全问题,解决方案有两种:1、在操作全局变量时,加上mutex,这对性能要求高的业务无非是致命一击。为了提高业务性能,内部所有的锁都使用了其他的方法来优化,比如mutex被双buffer代替,虽然mutex加锁和解锁的过程只有100ns。2.把上面的全局变量放到Json对象中,这样局部变量就不会崩溃了,但是这个方案有个问题,就是变化很大,需要很多严格的测试,所以放弃.因此,综合以上两点,我们决定采用其他更安全可靠的方法来解决线上闪退的问题。之所以使用rapidjson使用rapidjson是因为网上有几十种服务,其中大部分都使用rapidjson。由于历史原因,在线崩溃的服务只有少数服务使用jsoncpp。先介绍一下rapidjson,以下内容来自rapidjson官网:RapidJSONisaC++JSONparserandgenerator。它的灵感来自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(bigendian/littleendian),内部支持这些编码的检测、验证和转码。例如,RapidJSON可以在将UTF-8文件中的JSON字符串解析为DOM时将其转换为UTF-16。它还支持代理项对和“\u0000”(空字符)。但是,出于性能考虑,rapidjson需要格外小心使用。作者之前踩过类似的坑,将部分字符串赋值给了一个rapidjson对象。结果rapidjson并没有立即使用partialstring,而是在最后才访问partialstring的内容。此时,部分字符串已经发布。scope,导致rapidjson获取的内容出现乱码。结语在使用开源项目的时候,一定要做好调研。有需要的可以过源码实现(这个有点难??),不然很容易掉坑。作者在使用libcurl作为httpclient时,也触发了libcurl的bug,导致线上崩溃。当时连续两个晚上才解决。一入C++,深似海,XX从此过客。