大家好,我是雨乐!

前段时间,某个同事找我倾诉,说是因为strict weak ordering导致程序coredump,给公司造成数百万损失,最终评级故障为P0级,年终奖都有点不保了,听完不禁一阵唏嘘。

在之前的文章中,我们分析了std::sort的源码实现_,在数据量大时候,采用快排,分段递归排序。一旦分段后的数据量小于某个阈值,为了避免快排的递归调用引起的额外开销,此时就采用插入排序。如果递归层次过深,还会采用堆排序。_

今天,借助本文,我们分析下这次故障的原因,避免后面的开发过程中出现类似的问题。

背景

流量经过召回、过滤等一系列操作后,得到最终的广告候选集,需要根据相应的策略,进行排序,最终返回首位最优广告。

struct AdItem {
std::string ad_id;
int priority;
int score;
};

现在有一个AdItem类型的verctor,要求对其排序,排序规则如下:

  • 按照priority升序排列

  • 如果priority一样大,则按照score降序排列

需求还是比较简单吧,当时线上代码如下:

void AdSort(std::vector<AdItem> &ad_items) {
std::sort(ad_items.begin(), ad_items.end(), [](const AdItem &item1, const AdItem &item2) {
if (item1.priority < item2.priority) {
return true;
} else if (item1.priority > item2.priority) {
return false;
} return item1.score >= item2.score;
} );
}

测试环境构造测试case,符合预期,上线。

恐怖的事情来了,上线不久后,程序直接coredump,然后自动重启,接着有coredump,当时心情是这样的。

定位

第一件事,登录线上服务器,通过gdb查看堆栈信息

由于线上是release版的,看不了堆栈信息,将其编译成debug版,在某台线上进行灰度,不出意料,仍然崩溃,查看堆栈信息。

通过堆栈信息,这块的崩溃恰好是在AdSort函数执行完,析构std::vector的时候发生,看来就是因为此次上线导致,于是代码回滚,重新分析原因。

原因

为了尽快定位原因,将这块代码和线上的vector值获取出来,在本地构建一个小范围测试,基本代码如下:

oid AdSort(std::vector<AdItem> &ad_items) {
std::sort(ad_items.begin(), ad_items.end(), [](const AdItem &item1, const AdItem &item2) {
if (item1.priority < item2.priority) {
return true;
} else if (item1.priority > item2.priority) {
return false;
} return item1.score >= item2.score;
} );
} int main() {
std::vector<AdItem> v;
/*
给v进行赋值操作
*/ AdSort(v); return 0;
}

执行下面命令进行编译,并运行:

g++ -g test.cc -o test
./test

运行报错,如下:

通过gdb查看堆栈信息

线上问题复现,基本能够确认coredump原因就是因为AdSort导致,但是在AdSort中,就一个简单的排序,sort不可能出现崩溃,唯一的原因,就是写的lambda函数有问题。

利用_逐步定位排除法_,重新修改lambda函数,执行,运行正常。

void AdSort(std::vector<AdItem> &ad_items) {
std::sort(ad_items.begin(), ad_items.end(), [](const AdItem &item1, const AdItem &item2) {
if (item1.priority < item2.priority) {
return true;
} else if (item1.priority > item2.priority) {
return false;
} if (item1.score > item2.score) {
return true;
} return false;
} );
}

运行正常,那么就是因为lambda比较函数有问题,那么为什么这样就没问题了呢?

想起之前在<>中看到一句话_第21条:总是让比较函数在等值情况下返回false。应该就是没有遵循这个原则,才导致的coredump。

那么为什么要遵循这个原则呢?打开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函数中的排序算法,需要遵循严格弱序(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).

上面概念,总结下就是,存在两个变量x和y:

  • x > y 等同于 y < x
  • x == y 等同于 !(x < y) && !(x > y)

要想严格弱序,就需要遵循如下规则:

  • 对于所有的x:x < x永远不能为true,每个变量值必须等于其本身
  • 如果x < y,那么y < x就不能为true
  • 如果x < y 并且y < z,那么x < z,也就是说有序性必须可传递性
  • 如果x == y并且y == z,那么x == z,也就是说值相同也必须具有可传递性

那么,为什么不遵循严格弱序的规则,就会导致coredump呢?

对于std::sort(),当容器里面元素的个数大于_S_threshold的枚举常量值时,会使用快速排序

我们先看下sort的函数调用链(去掉了不会导致coredump的部分):

sort
-> __introsort_loop
--> __unguarded_partition

我们看下__unguarded_partition函数的定义:

template<typename _RandomAccessIterator, typename _Tp, typename _Compare>
_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 < __last))
return __first;
std::iter_swap(__first, __last);
++__first;
}
}

在上面代码中,有下面一段:

while (__comp(*__first, __pivot))
++__first;

其中,first为迭代器,pivot为中间值,comp为传入的比较函数。

如果传入的vector中,后面的元素完全相等,那么comp比较函数一直是true,那么后面++__first,最终就会使得迭代器失效,从而导致coredump。

好了,截止到此,此次线上故障原因分析完毕。

结语

这个故障,说真的,无话可说,只能怪自己学艺不精,心服口服,也算是给自己一个教训,后面test case尽可能跟线上一致,把问题尽早暴露在测试阶段。

这次把这个故障原因分享出来,希望大家在后面的开发过程中,能避免采坑。

好了,本期的文章就到这,我们下期见。

作者:高性能架构探索

扫描下方二维码关注公众号【高性能架构探索】,回复【pdf】免费获取计算机必备经典书籍

strict weak ordering导致公司级故障的更多相关文章

  1. c++ stl sort 自定义排序函数cmp要遵循 strict weak ordering

    满足strict weak ordering的运算符能够表达其他所有的逻辑运算符(logical operator): <(a, b)  : (a < b) <=(a, b): !( ...

  2. Strict Weak Ordering

    Description A Strict Weak Ordering is a Binary Predicate that compares two objects, returning true i ...

  3. ORACLE 回收站导致的故障

    ORACLE 回收站导致的故障 一.故障 (1)现象     一个生产环境,oracle数据库挂死,严重影响生产.查死锁sql,发现大量日志插入语句,并且每条运行时间都超过一分钟,插入非常缓慢.据分析 ...

  4. 【专】linux nameserver导致的故障

    前言: 大家都知道linux下添加dns服务器,修改/etc/resolv.conf,添加nameserver 119.29.29.29这样一行即可.但是胡乱添加nameserver也会导致故障 ,此 ...

  5. mysql5.5 物理删除binlog文件导致的故障

    故障现象: 中午12点多,一套主从集群的主库因为没有配置大页内存,发布时导致OOM,MYSQL实例重启了,然后MHA发生了切换.切换过程正常.切换后需要把原master配置成新master的slave ...

  6. IP-MAC绑定导致网络故障

    前段时间将一台服务器A的服务迁移至了另外一台服务器B,外网IP地址也顺带迁移过来了,结果网络出现了问题. 其中内网是畅通的,但是外网IP怎么都连不上另外一台路由C(B和C是在一个交换机下的,网段也相同 ...

  7. 《MySQL》一次MySQL慢查询导致的故障

    本文转载自 http://www.jb51.net/article/70955.htm 我们知道分析MySQL语句查询性能的方法除了使用EXPLAIN 输出执行计划,还可以让MySQL记录下查询超过指 ...

  8. RAC节点两边存储名字不一致导致的故障及相关延伸

    起因:一个客户的实际故障,该故障非常典型,其他客户类似的环境也非常多,所以很值得梳理并记录下来. 环境:Oracle 11.2.0.4 RAC(2 nodes)+ RHEL 6.6 共享存储:EMC ...

  9. nf_conntrack被启用导致服务故障

    1.查看是否有 nf_conntrack 使用iptable -L -t nat  查看,注意!!!查看也会启用nf_conntrack,任何和nat有关的命令都会启用nf_contrack -L 查 ...

随机推荐

  1. 锁对象-条件对象-synchronized关键字

    1 import java.util.concurrent.locks.Condition; 2 import java.util.concurrent.locks.Lock; 3 import ja ...

  2. 大数据处理系列之(一)Java线程池使用

    前言:最近在做分布式海量数据处理项目,使用到了java的线程池,所以搜集了一些资料对它的使用做了一下总结和探究, 前面介绍的东西大多都是从网上搜集整理而来.文中最核心的东西在于后面两节无界队列线程池和 ...

  3. 【编程思想】【设计模式】【创建模式creational 】工厂模式factory_method

    Python版 https://github.com/faif/python-patterns/blob/master/creational/factory_method.py #!/usr/bin/ ...

  4. matplotlib 画图中图和次坐标轴

    一: fig.add_axes 画图中图 fig = plt.figure() x = np.arange(1, 9, 1) y = np.linspace(1, 10, 8) left, botto ...

  5. 2.使用Lucene开发自己的搜索引擎–indexer索引程序中基本类介绍

    (1)Directory:Directory类描述了Lucene索引的存放位置,它是一个抽象,其子类负责具体制定索引的存储路径.FSDirectory.open方法来获取真实文件在文件系统中的存储路径 ...

  6. Jenkins实例 Maven项目

    目录 一.准备 二.创建项目 创建maven项目 源码管理部分 构建编译 Post Steps打包 构建后操作 三.测试 一.准备 先看初始化设置,如果做完初始化,则跳过 安装如下插件 Maven I ...

  7. Firebug: Net Panel 使用详解

    Introduction to Firebug: Net Panel Since there is not much user documentation related to Firebug fea ...

  8. CTF靶场

    CTF靶场测试报告 一.跨站脚本攻击(XSS) 实验原理:跨站脚本攻击( Cross Site Script),本来的缩写应为CSS,但是为了与层叠样式表(Cascading Style CSS)区分 ...

  9. 【论文笔记】 Denoising Implicit Feedback for Recommendation

    Denoising Implicit Feedback for Recommendation Authors: 王文杰,冯福利,何向南,聂礼强,蔡达成 WSDM'21 新加坡国立大学,中国科学技术大学 ...

  10. 40张图+万字,从9个数据类型帮你稳稳的拿捏Redis数据结构

    摘要:本文把Redis新旧版本的数据结构说图解一遍,共有 9 种数据结构:SDS.双向链表.压缩列表.哈希表.跳表.整数集合.quicklist.listpack. 本文分享自华为云社区<为了拿 ...