【线上问题】P1级公司故障,年终奖不保

2023-11-02 03:21

本文主要是介绍【线上问题】P1级公司故障,年终奖不保,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

对本文有疑问的,可以公众号【高性能架构探索】留言、私信,也可以加笔者微信直接交流;另外还有批量免费计算机电子书,后台回复[pdf]免费获取。

大家好,我是雨乐!

前段时间,某个同事找我倾诉,说是因为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尽可能跟线上一致,把问题尽早暴露在测试阶段。

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

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

这篇关于【线上问题】P1级公司故障,年终奖不保的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/327763

相关文章

怎样通过分析GC日志来定位Java进程的内存问题

《怎样通过分析GC日志来定位Java进程的内存问题》:本文主要介绍怎样通过分析GC日志来定位Java进程的内存问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、GC 日志基础配置1. 启用详细 GC 日志2. 不同收集器的日志格式二、关键指标与分析维度1.

Java进程异常故障定位及排查过程

《Java进程异常故障定位及排查过程》:本文主要介绍Java进程异常故障定位及排查过程,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、故障发现与初步判断1. 监控系统告警2. 日志初步分析二、核心排查工具与步骤1. 进程状态检查2. CPU 飙升问题3. 内存

Java 线程安全与 volatile与单例模式问题及解决方案

《Java线程安全与volatile与单例模式问题及解决方案》文章主要讲解线程安全问题的五个成因(调度随机、变量修改、非原子操作、内存可见性、指令重排序)及解决方案,强调使用volatile关键字... 目录什么是线程安全线程安全问题的产生与解决方案线程的调度是随机的多个线程对同一个变量进行修改线程的修改操

Redis出现中文乱码的问题及解决

《Redis出现中文乱码的问题及解决》:本文主要介绍Redis出现中文乱码的问题及解决,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录1. 问题的产生2China编程. 问题的解决redihttp://www.chinasem.cns数据进制问题的解决中文乱码问题解决总结

全面解析MySQL索引长度限制问题与解决方案

《全面解析MySQL索引长度限制问题与解决方案》MySQL对索引长度设限是为了保持高效的数据检索性能,这个限制不是MySQL的缺陷,而是数据库设计中的权衡结果,下面我们就来看看如何解决这一问题吧... 目录引言:为什么会有索引键长度问题?一、问题根源深度解析mysql索引长度限制原理实际场景示例二、五大解决

Springboot如何正确使用AOP问题

《Springboot如何正确使用AOP问题》:本文主要介绍Springboot如何正确使用AOP问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录​一、AOP概念二、切点表达式​execution表达式案例三、AOP通知四、springboot中使用AOP导出

Python中Tensorflow无法调用GPU问题的解决方法

《Python中Tensorflow无法调用GPU问题的解决方法》文章详解如何解决TensorFlow在Windows无法识别GPU的问题,需降级至2.10版本,安装匹配CUDA11.2和cuDNN... 当用以下代码查看GPU数量时,gpuspython返回的是一个空列表,说明tensorflow没有找到

解决未解析的依赖项:‘net.sf.json-lib:json-lib:jar:2.4‘问题

《解决未解析的依赖项:‘net.sf.json-lib:json-lib:jar:2.4‘问题》:本文主要介绍解决未解析的依赖项:‘net.sf.json-lib:json-lib:jar:2.4... 目录未解析的依赖项:‘net.sf.json-lib:json-lib:jar:2.4‘打开pom.XM

IDEA Maven提示:未解析的依赖项的问题及解决

《IDEAMaven提示:未解析的依赖项的问题及解决》:本文主要介绍IDEAMaven提示:未解析的依赖项的问题及解决,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝... 目录IDEA Maven提示:未解析的依编程赖项例如总结IDEA Maven提示:未解析的依赖项例如

Redis分片集群、数据读写规则问题小结

《Redis分片集群、数据读写规则问题小结》本文介绍了Redis分片集群的原理,通过数据分片和哈希槽机制解决单机内存限制与写瓶颈问题,实现分布式存储和高并发处理,但存在通信开销大、维护复杂及对事务支持... 目录一、分片集群解android决的问题二、分片集群图解 分片集群特征如何解决的上述问题?(与哨兵模