加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 百科 > 正文

c – 调试生产环境中的崩溃

发布时间:2020-12-16 07:08:12 所属栏目:百科 来源:网络整理
导读:首先,我应该给你一些背景信息.有问题的程序是 用C实现的一个相当典型的服务器应用穿过 项目,以及所有底层库中的错误 管理基于C异常. 我的问题与处理不可恢复的错误和/或相关 程序员错误—松散相当于“未经检查”的Java 例外,因为缺乏更好的并行.我特别感兴趣
首先,我应该给你一些背景信息.有问题的程序是
用C实现的一个相当典型的服务器应用穿过
项目,以及所有底层库中的错误
管理基于C异常.

我的问题与处理不可恢复的错误和/或相关
程序员错误—松散相当于“未经检查”的Java
例外,因为缺乏更好的并行.我特别感兴趣
在生产中处理这些条件的常见做法
环境.

特别是对于生产环境,有两个相互冲突的目标
出现上述类错误:易于调试
和可用性(在操作性能方面).每个
这些反过来表明了一个具体的策略:

>安装顶级异常处理程序以吸收所有未捕获的内容
例外,从而确保持续可用性.不幸,
这使得错误检查更加复杂,迫使程序员
依靠细粒度的日志记录或其他代码“仪器”
技术.
>尽可能地崩溃;这使得人们可以进行验尸
分析通过核心导致错误的条件
倾倒.当然,必须为系统提供恢复的手段
崩溃后及时操作,这可能是远的
从琐碎的.

所以我最终得到了两个半生不熟的解决方案;我想妥协一下
服务可用性和调试工具之间.我是什么
失踪 ?

注意:我已将问题标记为C特定,因为我感兴趣
解决方案和适用于它的特性;
尽管如此,我知道其他人会有很多重叠
语言/环境.

解决方法

免责声明:与服务器的OP I代码非常相似,因此整个答案都集中在这个特定的用例上.嵌入式软件或部署的应用程序的策略应该可能大不相同,不知道.

首先,这个问题有两个重要的(而且相当不同的)方面:

>放松调查(尽可能)
>确保恢复

让我们分开对待,因为分裂是征服.让我们从更艰难的一点开始吧.

确保恢复

使用C / Java样式的try / catch的主要问题是它很容易破坏你的环境,因为try和catch可以改变它们自己范围之外的东西.注意:与Rust和Go形成对比,其中任务不应与其他任务共享可变数据,并且失败将终止整个任务而无需恢复.

因此,有3种恢复情况:

>不可恢复:进程内存已损坏,无法修复
>可恢复,手动:可以在顶级处理程序中挽救该进程,但代价是重新初始化其大部分内存(缓存,……)
>可恢复,自动:好的,一旦我们到达顶级处理程序,该过程就可以再次使用了

崩溃可以最好地解决完全无法恢复的错误.实际上,在许多情况下(例如进程内存外部的指针),操作系统将有助于使其崩溃.不幸的是,在某些情况下它不会(悬空指针仍然指向你的进程内存),这就是内存损坏的发生方式.哎呀. Valgrind,Asan,Purify等……是旨在帮助您尽早发现这些不幸错误的工具;调试器会(稍微)帮助那些使它超过那个阶段的人.

可以恢复但需要手动清理的错误很烦人.在一些很少遇到的情况下你会忘记清理.因此应该静态地防止它.一个简单的转换(在顶级处理程序范围内移动缓存)允许您将其转换为可自动恢复的情况.

在后一种情况下,显然,您可以捕获,记录和恢复您的进程,等待下一个查询.您的目标应该是在生产中发生的唯一情况(如果甚至没有发生,则为cookie点).

缓解调查

注意:我将借此机会推广一个名为rr的Mozilla项目,该项目一旦成熟就可以真正地帮助调查.请查看本节末尾的快速说明.

毫不奇怪,为了调查你将需要数据.优选地,尽可能地,并且良好地排序/标记.

有两种(实践的)获取数据的方法:

>连续记录,以便在发生异常时,您拥有尽可能多的上下文
>异常日志记录,以便在异常时尽可能地进行日志记录

连续记录意味着性能开销和(当一切正常时)大量无用的日志.另一方面,异常日志记录意味着对系统能够在异常情况下执行某些操作具有足够的信任(在bad_alloc的情况下……哦).

一般来说,我会建议两者兼而有之.

连续记录

每个日志应包含:

>时间戳(尽可能精确)
>(可能)服务器名称,进程ID和线程ID
>(可能)查询/会话相关器
>此日志来自的文件名,行号和函数名称
>当然,一条消息,应该包含动态信息(如果你有静态消息,你可以用动态信息来丰富它)

什么值得记录?

至少I / O.所有输入,至少和输出都可以帮助发现与预期行为的第一个偏差. I / O包括:入站查询和相应的响应,以及与其他服务器,数据库,各种本地缓存,时间戳(与时间相关的决策)的交互,……

此类日志记录的目标是能够重现在控制环境中发现的问题(可以通过所有这些信息进行设置).作为奖励,它可以作为原始性能监视器使用,因为它在过程中提供了一些检查点(注意:我正在讨论监视而不是因为某个原因进行分析,这可以让您提出警报并发现大致,时间花了,但你需要更高级的分析才能理解为什么).

异常记录

另一种选择是丰富异常.作为原始异常的示例:std :: out_of_range产生以下原因(来自于什么):从libstdc的向量抛出时,vector :: _ M_range_check.

如果像我一样,vector是你选择的容器,那么这几乎是无用的,因此你的代码中有大约3,640个位置可能会被抛出.

获得有用例外的基础是:

>一条精确的消息:“访问大小为4的向量中的索引32”稍微有用,不是吗?
>调用堆栈:它需要特定于平台的代码来检索它,但是可以自动插入到基本异常构造函数中,所以去吧!

注意:一旦你的异常中有一个调用堆栈,你很快就会发现自己上瘾并将较少的第三方软件包装到适配器层中,只要将它们的异常翻译成你的;我们都做到了;)

除了这些基础知识之外,RAII还有一个非常有趣的功能:在展开过程中将注释附加到当前异常.一个简单的处理程序保留对变量的引用并检查异常是否在其析构函数中展开,一般只检查一个,并且在展开时执行所有重要的日志记录(但是,异常传播已经很昂贵,所以… ).

最后,你还可以丰富和重新抛出catch子句,但这很快就会用try / catch块来填充代码,所以我建议使用RAII代替.

注意:有一个原因是std异常不分配内存,它允许抛出异常,而抛出本身不会被std :: bad_alloc抢占;我建议有意识地选择有更丰富的异常,因为在尝试创建异常时(我还没有看到发生的异常)抛出std :: bad_alloc的可能性.你必须自己做出选择.

和延迟记录?

延迟日志记录背后的想法是,不像往常一样调用您的日志处理程序,而是延迟记录所有更细粒度的跟踪,并且只有在出现问题时才会到达它们(aka,exception).

因此,这个想法是分割日志:

>立即记录重要信息
>更细粒度的信息被写入暂存区,可以调用它以在发生异常时记录它们

当然,还有一些问题:

>碰撞时(最常)丢失便笺簿;你应该能够通过你的调试器访问它,如果你得到一个内存转储,虽然它不是那么愉快.
>便笺簿需要一个政策:什么时候丢弃它? (会话结束?交易结束?……),内存多少? (尽可能多?有界?…)
>性能成本是多少:即使不将日志写入磁盘/网络,格式化它们仍然需要花费!

我实际上从来没有使用过这样的便笺簿,因为我曾经拥有的所有非崩溃错误都只使用I / O日志记录和丰富的异常来解决.不过,如果我实施它,我会建议它:

>事务本地:由于记录了I / O,我们不需要更多的洞察力
>记忆有限:随着我们的进步,逐渐消除旧的痕迹
>日志级别驱动:就像常规日志记录一样,我希望能够只启用一些日志进入暂存区

和条件/概率记录?

每N个写一条迹线并不是很有趣;它实际上比任何东西都更令人困惑.另一方面,每N次深度记录一次事务可以提供帮助!

这里的想法是减少一般写入的日志量,同时仍然有机会在野外详细观察错误痕迹.减少通常由日志记录基础结构约束(传输和写入所有这些字节的成本)或软件的性能(格式化日志减慢软件速度)驱动.

概率记录的想法是在每个会话/事务开始时“翻转硬币”来决定它是快速的还是慢的:)

类似的想法(条件记录)是读取事务字段中的特殊调试字段,该字段启动完整日志记录(以速度为代价).

关于rr的快速说明

由于开销只有20%,并且这种开销仅适用于CPU处理,因此实际上值得系统地使用rr.然而,如果这不可行,那么在rr下启动N个服务器中的1个并且用于捕获难以发现的错误是可行的.

这类似于A / B测试,但是出于调试目的,可以通过客户的自愿承诺(交易中的标志)或概率方法来驱动.

哦,在一般情况下,当你没有追捕任何东西时,它可以很容易地完全停用.那么付20%就没有意义了.

这就是所有人

我可以为冗长的阅读道歉,但事实上我可能只是略读了这个主题.错误恢复很难.我希望评论和评论,以帮助改善这个答案.

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读