【線上問題】P1級公司故障,年終獎不保
?對本文有疑問的,可以公眾號留言、私信,也可以加筆者微信直接交流(文末留有微信二維碼);另外還有批量免費計算機電子書,后臺回復[pdf]免費獲取。
?
大家好,我是雨樂!
前段時間,某個同事找我傾訴,說是因為strict weak ordering導致程序coredump,給公司造成數(shù)百萬損失,最終評級故障為P0級,年終獎都有點不保了,聽完不禁一陣唏噓。
在之前的文章中,我們分析了std::sort的源碼實現(xiàn),在數(shù)據(jù)量大時候,采用快排,分段遞歸排序。一旦分段后的數(shù)據(jù)量小于某個閾值,為了避免快排的遞歸調(diào)用引起的額外開銷,此時就采用插入排序。如果遞歸層次過深,還會采用堆排序。
今天,借助本文,我們分析下這次故障的原因,避免后面的開發(fā)過程中出現(xiàn)類似的問題。
背景
流量經(jīng)過召回、過濾等一系列操作后,得到最終的廣告候選集,需要根據(jù)相應的策略,進行排序,最終返回首位最優(yōu)廣告。
struct?AdItem?{
??std::string?ad_id;
??int?priority;
??int?score;
};現(xiàn)在有一個AdItem類型的verctor,要求對其排序,排序規(guī)則如下:
按照priority升序排列
如果priority一樣大,則按照score降序排列
需求還是比較簡單吧,當時線上代碼如下:
void?AdSort(std::vector?&ad_items) ?{
?std::sort(ad_items.begin(),?ad_items.end(),?[](const?AdItem?&item1,?const?AdItem?&item2)?{
???if?(item1.priority???????return?true;
????}?else?if?(item1.priority?>?item2.priority)?{
??????return?false;
????}
????return?item1.score?>=?item2.score;
?}?);
}
測試環(huán)境構(gòu)造測試case,符合預期,上線。
恐怖的事情來了,上線不久后,程序直接coredump,然后自動重啟,接著有coredump,當時心情是這樣的。

定位
第一件事,登錄線上服務器,通過gdb查看堆棧信息

由于線上是release版的,看不了堆棧信息,將其編譯成debug版,在某臺線上進行灰度,不出意料,仍然崩潰,查看堆棧信息。

通過堆棧信息,這塊的崩潰恰好是在AdSort函數(shù)執(zhí)行完,析構(gòu)std::vector的時候發(fā)生,看來就是因為此次上線導致,于是代碼回滾,重新分析原因。
原因
為了盡快定位原因,將這塊代碼和線上的vector值獲取出來,在本地構(gòu)建一個小范圍測試,基本代碼如下:
void?AdSort(std::vector?&ad_items) ?{
std::sort(ad_items.begin(),?ad_items.end(),?[](const?AdItem?&item1,?const?AdItem?&item2)?{
??if?(item1.priority???????return?true;
????}?else?if?(item1.priority?>?item2.priority)?{
??????return?false;
????}
????return?item1.score?>=?item2.score;
}?);
}
int?main()?{
??std::vector?v;
??/*
??給v進行賦值操作
??*/
??AdSort(v);
??return?0;
}
執(zhí)行下面命令進行編譯,并運行:
g++?-g?test.cc?-o?test
./test
運行報錯,如下:

通過gdb查看堆棧信息

線上問題復現(xiàn),基本能夠確認coredump原因就是因為AdSort導致,但是在AdSort中,就一個簡單的排序,sort不可能出現(xiàn)崩潰,唯一的原因,就是lambda函數(shù)實現(xiàn)有問題。
利用逐步定位排除法,重新修改lambda函數(shù),執(zhí)行,運行正常。
void?AdSort(std::vector?&ad_items) ?{
?std::sort(ad_items.begin(),?ad_items.end(),?[](const?AdItem?&item1,?const?AdItem?&item2)?{
???if?(item1.priority???????return?true;
????}?else?if?(item1.priority?>?item2.priority)?{
??????return?false;
????}
????
????if?(item1.score?>?item2.score)?{
??????return?true;
????}
????return?false;
?}?);
}
運行正常,那么就是因為lambda比較函數(shù)有問題,那么為什么這樣就沒問題了呢?
想起之前在
那么為什么要遵循這個原則呢?打開Google,輸入std::sort coredump,看到了一句話
?Having a non-circular relationship is called non-transitivity for the
?<operator. It’s not too hard to realise that if your relationships are circular then you won’t be getting reasonable results. In fact there is a very strict set of rules that a data type and its comparators must abide by in order to get correct results from C++ STL algorithms, that is 「strict weak ordering」.
從上面的意思看,在STL中,對于sort函數(shù)中的排序算法,需要遵循嚴格弱序(strict weak ordering)的原則。
嚴格弱序
什么是嚴格弱序呢?摘抄下來自wikipedia的定義:
?A 「strict weak ordering」 is a binary relation < on a set S that is a strict partial order (a transitive relation that is irreflexive, or equivalently,[5] that is asymmetric) in which the relation "neither a < b nor b < a" is transitive.[1] Therefore, a strict weak ordering has the following properties:
?
For all x in S, it is not the case that x < x (irreflexivity). For all x, y in S, if x < y then it is not the case that y < x (asymmetry). For all x, y, z in S, if x < y and y < z then x < z (transitivity). For all x, y, z in S, if x is incomparable with y (neither x < y nor y < x hold), and y is incomparable with z, then x is incomparable with z (transitivity of incomparability).
上面概念,總結(jié)下就是,存在兩個變量x和y:
x > y 等同于 ?y < x x == y 等同于 !(x < y) && !(x > y)
要想嚴格弱序,就需要遵循如下規(guī)則:
對于所有的x:x < x永遠不能為true,每個變量值必須等于其本身 如果x < y,那么y < x就不能為true 如果x < y 并且y < z,那么x < z,也就是說有序性必須可傳遞性 如果x == y并且y == z,那么x == z,也就是說值相同也必須具有可傳遞性
那么,為什么不遵循嚴格弱序的規(guī)則,就會導致coredump呢?
?對于
?std::sort(),當容器里面元素的個數(shù)大于_S_threshold的枚舉常量值時,會使用快速排序,在STL中這個值的默認值是16
我們先看下sort的函數(shù)調(diào)用鏈(去掉了不會導致coredump的部分):
sort
->?__introsort_loop
-->?__unguarded_partition
我們看下__unguarded_partition函數(shù)的定義:
template
?????_RandomAccessIterator
?????__unguarded_partition(_RandomAccessIterator?__first,
???????????????_RandomAccessIterator?__last,
???????????????_Tp?__pivot,?_Compare?__comp)
?????{
???????while?(true)
?????{
???????while?(__comp(*__first,?__pivot))
?????????++__first;
???????--__last;
???????while?(__comp(__pivot,?*__last))
?????????--__last;
???????if?(!(__first??????????return?__first;
???????std::iter_swap(__first,?__last);
???????++__first;
?????}
?????}
在上面代碼中,有下面一段:
while?(__comp(*__first,?__pivot))
?????????++__first;
其中,__first為迭代器,__pivot為中間值,__comp為傳入的比較函數(shù)。
如果傳入的vector中,后面的元素完全相等,那么__comp比較函數(shù)一直是true,那么后面++__first,最終就會使得迭代器失效,從而導致coredump。
好了,截止到此,此次線上故障原因分析完畢。
此次故障,由于牽扯到算法、工程等部門,由于一開始的時候,不確定問題出在哪(一方面線上是release版本,一方面涉及到多個模塊的改動),幾個部門聯(lián)合分析,最終才定位到bug原因,期間曲折過程略去不表。
結(jié)語
這個故障,說真的,無話可說,只能怪自己學藝不精,心服口服,也算是給自己一個教訓,后面test case盡可能跟線上一致,把問題盡早暴露在測試階段。
這次把這個故障原因分享出來,希望大家在后面的開發(fā)過程中,能夠避免遇到同樣的問題。
