"工欲善其事,必先利其器"
背景
我们经常会进行一些线上的压测,然后来分析一些问题,比如大内存的分析,cpu 飙升问题的排查,来定位引线上问题的原因,从而解决问题。
往往这些数据,很难从我们可见的一些监控项,来进行排查出来,这时候,就得通过一些工具,来进行排查分析总结,过程中避免不了线上的大型操作,比如 dump 、jrf 等操作,但命令慎用,因为会对线上造成一定的影响。
案例
就本次的压测情况,进行下分析 ,上周四,针对 服务进行了一次压测,在压测过程中发现,很容易打爆了 内存,如下图
基于当前的案例背景,来分析下,问题原因,以及如何进行排查的过程
过程
- 通过 cat 和 grafana 上监控看到了, 内从使用的飙升。
- 由于无法在线上直接进行 分析,就下载 dump 文件,进行本地的 内存分析。过了一定的事故现场,下载下来生成的 dump 文件不一定能恢复当时实际的 事故现场,单也能从一定程度上进行反应。
通过 jprofiler 对 dump 文件来进行一些分析,排查大内存 的占用是属于那些地方引起导致的
- 通过 jprofiler 打开之后,堆内存 分析 如下图,发现大内存的调用 是大量的字符类型数组
- 然后通过此刻大对象 的这个视图,可以看到,大部分内存占用 在哪里此刻可以排查到,大对象的占用,全都是 LogData 对象
- 针对 char[] 这个对象,展开之后看他的引用,如同下图,发现大批量的都是一些 返回值 & context 等上下文中的东西
此刻已经大部分明了其组成。
- 针对其中的几条,抽样 进行 查看
- 通过 show in graph 可以方便的定位出来最上层的引用,从而方便排查问题
如下图所示
通过对象的生成和引用关系,可以轻松的定位出来,,主要的问题出现在 2 个地方,
- 一个是数据 trheadLocal 中 的 返回值 。
- 还有一个是属于 LogData 中的 大对象。
此刻,问题已经清晰,绝大多的数的 大对象,来自于 LogHelper ,占整体的 61%
接着,在代码中定位查看 :在代码中 会持有一个 blockingQueue 队列,队列长度10 w ,目前 已经改为了 1 万
会死循环的方式,针对队列汇总的日志数据,进行 日志的上报,此刻针对 流程中的节点日志出入参,进行上报,用来进行问题的排
通过日志的上报代码发现 ,是通过 http 的方式进行上报Okhttp client 设置 超时时间 3s 然后一次 100 条,当 上报失败,会重试一次 ,2 次失败,则丢弃
总结
当时压测的时候,在大流量的情况下, 基本上把日志收集服务打垮了,此刻平均 6s 中 消费 100 条队列中的数据。消费速度远远赶不上 生成速度,所以大批量的信息,被持有在 内存中,无法释放,引起频繁的 GC 和 内存飙升。
大问题的分析出来之后,又对 一些细节问题记性分析:
- 为什么这个单条日志信息这么大针对大日志的问题,当晚压测已经有分析出来,我们针对节点 上下文中,部分会记性重复写数据进去,在其中一块的业务代码逻辑中会重写一个 list 对象如下图
- 为什么日志会这么多?捞取其中的一条 大日志,返现这一批中,会出现几条一样的 traceId 的数据,traceId 一致的数据,则以为这一次调用,不同的 processor通过对比工具看来,2 个节点 ,具备一样的 input ,从而分析出,即使不同的节点,凡是写在 context 上下文中的对象,会无限制的膨胀。
- 通过组件的框架的代码,也验证了这一点,每次的 input 都会 序列化 当前的对象,并不是对象之间的引用
至此,得出结论: 我们的上下文有多大,有 n 个节点,那么日志就会膨胀 n 倍,内存中的持有,也会膨胀,可能还会导致由于数据包过大,而带来的上报异常
最后也反哺工具团队,进行部分代码的优化