为什么我建议线上高并发量的日志输出的时候不能带有代码位置(下)

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介: 为什么我建议线上高并发量的日志输出的时候不能带有代码位置(下)

Log4j2 中是如何获取堆栈的


我们先来自己思考下如何实现:首先 Java 9 之前,获取当前线程(我们这里没有要获取其他线程的堆栈的情况,都是当前线程)的堆栈可以通过:


image.png


其中 Thread.currentThread().getStackTrace(); 的底层其实就是 new Exception().getStackTrace(); 所以其实本质是一样的。

Java 9 之后,添加了新的 StackWalker 接口,结合 Stream 接口来更优雅的读取堆栈,即:


image.png


我们先来看看 new Exception().getStackTrace(); 底层是如何获取堆栈的:

javaClasses.cpp


image.png


然后是 StackWalker,其核心底层源码是:


image.png


可以看出,核心都是填充堆栈详细信息,区别是一个直接填充所有的,一个会减少填充堆栈信息。填充堆栈信息,主要访问的其实就是 SymbolTable,StringTable 这些,因为我们要看到的是具体的类名方法名,而不是类的地址以及方法的地址,更不是类名的地址以及方法名的地址。那么很明显:通过 Exception 获取堆栈对于 Symbol Table 以及 String Table 的访问次数要比 StackWalker 的多,因为要填充的堆栈多

我们接下来测试下,模拟在不同堆栈深度下,获取代码执行会给原本的代码带来多少性能衰减


模拟两种方式获取调用打印日志方法的代码位置,与不获取代码位置会有多大性能差异


以下代码我参考的 Log4j2 官方代码的单元测试,首先是模拟某一调用深度的堆栈代码:


image.png


然后,编写测试代码,对比纯执行这个代码,以及加入获取堆栈的代码的性能差异有多大。


微信图片_20220625200407.jpg


执行:查看结果:


image.png


从结果可以看出,获取代码执行位置,也就是获取堆栈,会造成比较大的性能损失。同时,这个性能损失,和堆栈填充相关。填充的堆栈越多,损失越大。可以从 StackWalker 的性能优于通过异常获取堆栈,并且随着堆栈深度增加差距越来越明显看出来


为何会慢?String::intern 带来的性能衰减程度测试


这个性能衰减,从前面的对于底层 JVM 源码的分析,其实可以看出来是因为对于 StringTable 以及 SymbolTable 的访问,我们来模拟下这个访问,其实底层对于 StringTable 的访问都是通过 String 的 intern 方法,即我们可以通过 String::intern 方法进行模拟测试,测试代码如下:


微信图片_20220625200501.jpg


测试结果:


微信图片_20220625200507.jpg



对比 StackWalkBenchmark.baselineStackWalkBenchmark.toString 的结果,我们看出 bh.consume(time); 本身没有什么性能损失。但是通过将他们与 StackWalkBenchmark.intern 以及 StackWalkBenchmark.intern3 的结果对比,发现这个性能衰减,也是很严重的,并且访问的越多,性能衰减越严重(类比前面获取堆栈)。


结论与建议


由此,我们可以得出如下直观的结论:

  1. 日志中输出代码行位置,Java 9 之前通过异常获取堆栈,Java 9 之后通过 StackWalker
  2. 两种方式都需要访问 SymbolTable 以及 StringTable,StackWalker 可以通过减少要填充的堆栈来减少访问量
  3. 两种方式对于性能的衰减都是很严重的

由此,我建议:对于微服务环境,尤其是响应式微服务环境,堆栈深度非常深,如果会输出大量的日志的话,这个日志是不能带有代码位置的,否则会造成严重的性能衰减

我们在关闭输出代码行位置之后,同样压力下,CPU 占用不再那么高,并且整体吞吐量有了明显的提升。



相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
相关文章
|
5月前
|
SQL 存储 监控
|
6月前
|
Java
使用Java代码打印log日志
使用Java代码打印log日志
310 1
|
5月前
|
存储 算法 Go
go语言并发实战——日志收集系统(七) etcd的介绍与简单使用
go语言并发实战——日志收集系统(七) etcd的介绍与简单使用
|
6月前
|
C++ 开发者 Python
实现Python日志点击跳转到代码位置的方法
本文介绍了如何在Python日志中实现点击跳转到代码位置的功能,以提升调试效率。通过结合`logging`模块的`findCaller()`方法记录代码位置信息,并使用支持点击跳转的日志查看工具(如VS Code、PyCharm),开发者可以从日志直接点击链接定位到出错代码,加快问题排查。
95 2
08-06-06>pe_xscan 精简log分析代码 速度提升一倍
08-06-06>pe_xscan 精简log分析代码 速度提升一倍
|
2月前
|
SQL 安全 数据库
基于SQL Server事务日志的数据库恢复技术及实战代码详解
基于事务日志的数据库恢复技术是SQL Server中一个非常强大的功能,它能够帮助数据库管理员在数据丢失或损坏的情况下,有效地恢复数据。通过定期备份数据库和事务日志,并在需要时按照正确的步骤恢复,可以最大限度地减少数据丢失的风险。需要注意的是,恢复数据是一个需要谨慎操作的过程,建议在执行恢复操作之前,详细了解相关的操作步骤和注意事项,以确保数据的安全和完整。
96 0
|
3月前
|
消息中间件 Kubernetes Kafka
微服务从代码到k8s部署应有尽有系列(十一、日志收集)
微服务从代码到k8s部署应有尽有系列(十一、日志收集)
|
3月前
分享一份 .NET Core 简单的自带日志系统配置,平时做一些测试或个人代码研究,用它就可以了
分享一份 .NET Core 简单的自带日志系统配置,平时做一些测试或个人代码研究,用它就可以了
|
5月前
|
监控 Go
go语言并发实战——日志收集系统(八) go语言操作etcd以及利用watch实现对键值的监控
go语言并发实战——日志收集系统(八) go语言操作etcd以及利用watch实现对键值的监控
go语言并发实战——日志收集系统(八) go语言操作etcd以及利用watch实现对键值的监控
|
5月前
|
存储 Go 索引
go语言并发实战——日志收集系统(一) 项目前言
go语言并发实战——日志收集系统(一) 项目前言
go语言并发实战——日志收集系统(一) 项目前言