日志对性能的影响以及我的几个日志习惯

 关注公众号【1024个为什么】,及时接收最新推送文章! 

本文内容:

1、背景

2、影响性能的日志因素

3、好的日志习惯

||  背景

写这篇文章有 2 个原因,一是前段时间架构组同事的一次性能优化分享,单单日志(log4j2)这一项优化性能就提升了 19 倍,QPS 从1660 升到 32000,被震撼到了。

二是最近接手的一个项目中,日志加了彩色打印,按日志的级别设置了不同的高亮颜色,看得我眼花缭乱,用 less 等一些命令查看时还会展示出来一堆 "ESC[m]",这让有点强迫症的我看着很不爽。

趁着技术优化,把这块也改一下,自己先爽了再说。顺便也重新认识一下在各种算法、秒杀设计大行其道的当下,这个不太起眼的小家伙。

||  影响性能的日志因素

| 位置信息

官网称作 Location Information,就是我们配置文件里的这类信息(%c{3}#%M %L),含义是当前这行日志是哪个类的哪个方法哪一行打印的。输出效果如下:

cashpool.task.OrderReceiveTask#run 78

可配置的模式有很多,具体见官网 https://logging.apache.org/log4j/2.x/manual/layouts.html#Patterns 。

这里只说和位置相关的  %C or %class, %F or %file, %l or %location, %L or %line, %M or %method。

官网这几个模式的说明中也都反复强调了会影响性能。同时也给出了具体的性能数据,比常用的同步 logger 慢 1.3 ~ 5 倍。如果在异步 logger 中使用位置信息,将会慢 30 ~ 100 倍。

Generating line number information (location information) is an expensive operation and may impact performance. Use with caution.

为什么会这么慢呢?

我们都知道,java 程序执行时,每个线程都会有自己的栈,每个方法都会生成一个 frame,要获取位置信息,就要获取当前线程的栈帧信息,java 9 之前提供了两种获取栈帧的方法

java.lang.Throwable#getStackTrace()
java.lang.Thread#getStackTrace()

java 9 提供了 StackWalker 类,有网友说它的性能要好一些,我没找到有力证据,看到的都是介绍跳帧的功能,有兴趣的可以深究一下。

我们就看一下 java 9 之前的 2 种方法:

c6b78ea6d2a5f9779bd8012693f18708.png

2a0f55419b7add7f3ffb3a8a65302413.png

有网友说 java.lang.Throwable#getStackTrace() 的性能要好一些,我也没有找到直接证据,但从调用底层 native 的方法名字看 Thread 里调用的是 dumpThreads ,看到 dump 字眼,就会联想到 stop the world ,如果真挂起线程,那效率就低了。

而且 log4j 里用的是java.lang.Throwable#getStackTrace()。

d3d0550199e37c8fd31a8a1069e16c0c.png

不管是哪种方法获取堆栈信息,应该都不会太高效。

想想我们一个请求,从框架层面交给一个线程后,动辄几十层方法才能调到我们的业务代码太正常不过了。

既然这么低效,那我们不打位置信息,有问题怎么定位呢?

我仔细回想了一下过往排查问题时,好像都是通过日志内容,定位到哪一行代码,几乎没有通过类名行号去定位代码,编译后的行号准不准确也另说。

其实我们的主要目的是不通过框架获取堆栈的形式打印位置信息,我们完全可以在日志的内容中带上位置信息,通过一个切面就能搞定的事。

| 不同的 Appender

我们工作中一般都是把日志输出到文件中的,我们就挑 3 个文件相关的 Appender 说明一下,不同的 Appender 性能的差异主要在 I/O 上。

FileAppender RollingFileAppender   内部使用的都是 BufferedOutputStream。

RandomAccessFileAppender 内部使用了 ByteBuffer + RandomAccessFile 技术,与 FileAppender 相比,性能提高了20 ~ 200%。

AsyncAppender 它不能独立存在,要依赖其他的 Appenders,配置在它们之后。

它只是新起一个线程中把 LogEvents 交给了它所依赖的 Appenders。默认内部使用的是 ArrayBlockingQueue ,多线程并发打日志时,性能可能会变得更差。这种场景官方推荐使用无锁的 Asynchronous Loggers 。

Asynchronous Loggers,它是 log4j2 新提供的功能,通过新起线程执行 I/O 操作来提升性能,底层使用的是  Disruptor 框架,通过无锁线程通信,代替了 ArrayBlockingQueue 。

它支持所有 Loggers 异步处理,也支持同步、异步 组合使用。可靠性要求高的比如异常信息就可以配成同步的,其他配成异步的。

每个 Appender 的具体用法可以从官网查看,

https://logging.apache.org/log4j/2.x/manual/appenders.html

| 不同的刷盘策略

上面说到的 Appenders 的配置项中,都有一个 “immediateFlush”,默认 = false,日志文件不像数据库那样追求高可靠性,可以忽略此配置,知道配置为 true 性能会变差就行。

貌似所有涉及到刷盘的技术,都会提供这类的配置项,这里就不多说了。

||  好的日志习惯

| 日志格式统一

这里主要指日志内容中各个元素的顺序,统一的日志格式,不仅查看起来方便、高效,而且如果有通过日志做一些筛选、统计类的需求,可以轻松搞定。

| 日志文件大小

如果访问量不是很大,一天也就几百兆,个人觉得完全没必要按大小或者按小时拆分日志文件。

反倒是决定拆分文件之前,更应该看看日志打的是否合理,是不是打了很多无用日志导致的。

| 不打无用日志

日志内容,能少就少,不在循环中打,大 list 简化,不打无用内容。

比如日志里的时间,[年-月-日] 是不是可以不打,文件名上带年月日就行,这样每行就能省 10 个字符,如果只打时分秒很难受,可以加上[月-日],年真没必要加。

还有线程名,打出来的都是线程池里的线程名称,没有任何意义,有 traceId 过滤出一次请求的日志就可以了,这个不打又可以省一二十个字符。

单靠这几点小优化后,日志文件就缩小了 20%。

| 关键信息提到最前

很多告警都是通过监控日志关键字触发的,然后发消息给负责人。但消息内容一般都很长,可能包含前后行的信息,如果前行内容长,消息再做省略处理,你收到消息后啥有用的内容也看不到了,只知道收到了报警,具体信息还要去看日志。

我们需要把最关键的信息打在最前面,尤其像失败原因之类的,理想的效果就是看到报警消息就知道问题原因,不用再去查日志了。

| 敏感信息和日期

我们打的日志中难免会包含手机号、银行卡号等敏感信息,这些都要脱敏的。

还有日志里打出来的业务时间,如果不做特殊处理,一般都是一个毫秒串,需要知道具体时间的话还得再通过工具转换一次,很不方便。

我们打日志一般都是 JSON 串,这 2 个问题都可以统一解决,通过 fastjson 的 Filter 就能轻松搞定。

脱敏需要自己写 Filter 和 脱敏规则,日期就用 fastjson  自带的 SerializerFeature#WriteDateUseDateFormat。

| 用好 debug 级别

我发现很多人当然也包括我在内,都不太用 debug 级别的日志,就像大家都不喜欢写单测一样。

其实 debug 日志是一个很好的习惯,不仅在联调测试过程中方便定位问题,也不会造成生成环境过多无用的日志,还有助于代码的阅读,何乐不为。

| 用好切面日志

我们一般加切面日志,都是不加区分的把方法的入参、返回结果统统打印出来,而且有的还控制层、服务层等层层打印,尤其像返回结果是 List 的,就造成很多无用日志。

所以在加切面的时候是不是可以加上日志级别、返回类型的考虑,用不用每层都加切面。

前面最开始提到的位置信息,就可以通过切面解决,即不损失功能,也提高了性能。

扯两句

做一个“抠门”的程序员,性能应该不会太差吧

你忽略掉的,往往是重要的

原创不易,如有收获,一键三连,感谢支持!

参考文献:

https://docs.oracle.com/javase/9/docs/api/java/lang/StackWalker.html

https://stackoverflow.com/questions/2347828/how-expensive-is-thread-getstacktrace

https://zhuanlan.zhihu.com/p/471819212

 https://logging.apache.org/log4j/2.x/manual/layouts.html#LocationInformation

https://logging.apache.org/log4j/2.x/manual/layouts.html#Patterns

https://logging.apache.org/log4j/2.x/manual/appenders.html

https://logging.apache.org/log4j/2.x/performance.html#asyncLogging

  • 3
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 学习后端不是一件容易的事情,但是可以通过以下几个步骤来帮助你:1、了解后端的基础概念,如数据库、网络、服务器等;2、学习常见的后端语言,如PHP、javapython等;3、学习后端开发框架,如Django、Springboot等;4、掌握一些常用的后端技术,如数据库优化、安全性、测试等。希望以上建议能够帮助到你。 ### 回答2: 作为前端开发,如果你想学习后端开发,以下是一些建议和规划: 1. 首先你需要了解后端开发的一些基础知识。后端开发通常涉及服务器端语言(如JavaPython、PHP等)以及数据库的使用。你可以开始学习一门后端语言,选择一门适合你的语言进行深入学习。 2. 学习服务器端框架:后端开发通常使用框架来简化开发过程。根据你选择的后端语言,选择一个主流的框架,如Java的Spring、Python的Django等,并深入掌握它们的使用方法和原理。 3. 学习数据库:后端开发必不可少的是数据库的使用。熟悉关系型数据库(如MySQL、Oracle)和非关系型数据库(如MongoDB、Redis)的基本概念和使用方法,了解数据库的设计和优化原则。 4. 学习API开发和RESTful架构:后端开发通常涉及与前端进行数据交互,掌握API的开发和设计原则是必要的。了解RESTful架构和API开发的基本原理和实践。 5. 学习网络和安全知识:后端开发涉及网络通信和数据传输,了解网络协议、HTTP、HTTPS等基础知识是有帮助的。此外,了解常见的安全漏洞和安全防护知识,以提高你的代码质量和系统安全性。 6. 实践项目:通过实际的项目经验来巩固所学知识。你可以尝试模仿已有的项目,或者参与开源项目,这样可以让你更好地理解和运用所学技术。 7. 持续学习和保持更新:后端开发是一个不断演进的领域,新的技术和工具不断涌现。要保持持续学习的习惯,关注行业动态,参加相关的培训或研讨会。 总之,学习后端开发需要全面的基础知识和实践经验。通过系统学习和不断实践,你可以逐步掌握后端开发的技能,扩展自己的技术能力。 ### 回答3: 学习后端开发是提高技能和拓宽职业发展的重要一步。下面是一个详细的学习后端开发的规划: 1. 建立基础知识 - 了解后端开发的基本概念和原则,包括服务器、数据库、API等。 - 研究不同的后端编程语言和框架,如Java(Spring Boot)、Python(Django)、Node.js等,选择一门你感兴趣且流行的编程语言作为起点。 2. 掌握数据库 - 学习使用关系型数据库(如MySQL、PostgreSQL)和非关系型数据库(如MongoDB、Redis)。 - 学习使用SQL语言,掌握数据库的设计和优化原则,了解索引、事务和存储过程等概念。 3. 学习服务器配置和部署 - 了解Linux操作系统基础知识,学习常用命令和文件管理。 - 研究Web服务器(如Nginx、Apache)的配置和部署,学习如何管理并优化服务器。 4. 掌握后端框架和开发工具 - 学习使用你选择的后端开发语言的框架,如Spring、Django、Express等。 - 学习使用版本控制工具(如Git)和项目管理工具(如Maven、npm)。 - 掌握调试工具、日志工具和性能分析工具的使用。 5. 实践项目 - 根据你的兴趣和经验,选择一个小型后端项目作为练习和实践。 - 通过实践项目,锻炼解决问题的能力和项目管理能力。 - 参与开源项目或者团队协作项目,获得更多经验和工作机会。 6. 持续学习和深造 - 关注后端开发的前沿技术和趋势,参与行业交流和技术讨论。 - 学习设计模式、算法和系统设计,不断提升自己的技术水平。 - 考虑参加相关的培训课程或获得相关认证,如Oracle Certified Professional Java Developer等。 总之,学习后端开发需要掌握基础知识、数据库、服务器配置和部署、后端框架和工具等,同时通过实践项目和持续学习来提升自己的能力。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值