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

本文涉及的产品
日志服务 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日志并进行多维度分析。
相关文章
|
7月前
|
SQL 存储 监控
|
1月前
|
监控 测试技术 开发者
一行代码改进:Logtail的多行日志采集性能提升7倍的奥秘
一个有趣的现象引起了作者的注意:当启用行首正则表达式处理多行日志时,采集性能出现下降。究竟是什么因素导致了这种现象?本文将探索Logtail多行日志采集性能提升的秘密。
145 23
|
1月前
|
运维 监控 Cloud Native
一行代码都不改,Golang 应用链路指标日志全知道
本文将通过阿里云开源的 Golang Agent,帮助用户实现“一行代码都不改”就能获取到应用产生的各种观测数据,同时提升运维团队和研发团队的幸福感。
167 12
|
7月前
|
存储 算法 Go
go语言并发实战——日志收集系统(七) etcd的介绍与简单使用
go语言并发实战——日志收集系统(七) etcd的介绍与简单使用
08-06-06>pe_xscan 精简log分析代码 速度提升一倍
08-06-06>pe_xscan 精简log分析代码 速度提升一倍
|
4月前
|
SQL 安全 数据库
基于SQL Server事务日志的数据库恢复技术及实战代码详解
基于事务日志的数据库恢复技术是SQL Server中一个非常强大的功能,它能够帮助数据库管理员在数据丢失或损坏的情况下,有效地恢复数据。通过定期备份数据库和事务日志,并在需要时按照正确的步骤恢复,可以最大限度地减少数据丢失的风险。需要注意的是,恢复数据是一个需要谨慎操作的过程,建议在执行恢复操作之前,详细了解相关的操作步骤和注意事项,以确保数据的安全和完整。
226 0
|
5月前
|
消息中间件 Kubernetes Kafka
微服务从代码到k8s部署应有尽有系列(十一、日志收集)
微服务从代码到k8s部署应有尽有系列(十一、日志收集)
|
7月前
|
监控 Go
go语言并发实战——日志收集系统(八) go语言操作etcd以及利用watch实现对键值的监控
go语言并发实战——日志收集系统(八) go语言操作etcd以及利用watch实现对键值的监控
go语言并发实战——日志收集系统(八) go语言操作etcd以及利用watch实现对键值的监控
|
7月前
|
存储 Go 索引
go语言并发实战——日志收集系统(一) 项目前言
go语言并发实战——日志收集系统(一) 项目前言
go语言并发实战——日志收集系统(一) 项目前言
|
5月前
分享一份 .NET Core 简单的自带日志系统配置,平时做一些测试或个人代码研究,用它就可以了
分享一份 .NET Core 简单的自带日志系统配置,平时做一些测试或个人代码研究,用它就可以了

热门文章

最新文章

  • 1
    Nginx实现高并发
    91
  • 2
    高并发场景下,到底先更新缓存还是先更新数据库?
    96
  • 3
    Java面试题:解释Java NIO与BIO的区别,以及NIO的优势和应用场景。如何在高并发应用中实现NIO?
    96
  • 4
    Java面试题:设计一个线程安全的单例模式,并解释其内存占用和垃圾回收机制;使用生产者消费者模式实现一个并发安全的队列;设计一个支持高并发的分布式锁
    83
  • 5
    Java面试题:如何实现一个线程安全的单例模式,并确保其在高并发环境下的内存管理效率?如何使用CyclicBarrier来实现一个多阶段的数据处理任务,确保所有阶段的数据一致性?
    83
  • 6
    Java面试题:结合建造者模式与内存优化,设计一个可扩展的高性能对象创建框架?利用多线程工具类与并发框架,实现一个高并发的分布式任务调度系统?设计一个高性能的实时事件通知系统
    70
  • 7
    Java面试题:假设你正在开发一个Java后端服务,该服务需要处理高并发的用户请求,并且对内存使用效率有严格的要求,在多线程环境下,如何确保共享资源的线程安全?
    87
  • 8
    在Java中实现高并发的数据访问控制
    55
  • 9
    使用Java构建一个高并发的网络服务
    39
  • 10
    微服务06----Eureka注册中心,微服务的两大服务,订单服务和用户服务,订单服务需要远程调用我们的用,户服务,消费者,如果环境改变,硬编码问题就会随之产生,为了应对高并发,我们可能会部署成一个集
    60