龙空技术网

浅析JAVA日志中的几则性能实践与原理解释

阿里云开发者 588

前言:

现时你们对“java中日志的作用”大约比较注意,朋友们都想要知道一些“java中日志的作用”的相关资讯。那么小编也在网络上搜集了一些有关“java中日志的作用””的相关文章,希望小伙伴们能喜欢,朋友们快快来学习一下吧!

点击链接阅读原文,获取更多技术内容:浅析JAVA日志中的几则性能实践与原理解释-阿里云开发者社区

本篇文章通过几个技术点说明日志记录过程中的性能实践,计算机领域的性能往往都遵循着冰山法则,即你能看得见的、程序员能感知的只是其中的一小部分,还有大量的细节隐藏在冰山之下。

作者 | 骄龙、孤弋

来源 | 阿里云开发者公众号

前言

程序记录日志的过程,就是将需要记录的内容写入到磁盘文件中的过程。与生活的物流场景类似,好比是一车货物通过一套运输体系运送至目的地的过程,然而在这套物流体系中,我们往往不需要自己完成整套打包、上车、运输、卸货等全套流程,只需要将包打好之后交由专业的物流公司即可。对于我们今天所要描述的日志场景而言,日志内容是需要运送的货物,日志框架就是物流公司,而目的地就是磁盘上的文件(或其他日志收集服务器)。在 Java 的语言体系中,针对日志处理很早有了很好的日志框架 log4jlogback以及 jul(Java Util Logging) 等,这些框架替我们隐藏了日志记录的技术细节,程序员只需要使用 Logger这一个工具类,即可高效的完成业务日志的记录,如下面代码所示:

Logger logger = LogFactory.getLogger("PoweredByEDAS");String product = "EDAS";logger.info("This is powered by product: " + product);

这一篇文章是想通过几个技术点来说明日志记录过程中的性能实践,计算机领域的性能往往都遵循着冰山法则,即你能看得见的、程序员能感知的只是其中的一小部分,还有大量的细节隐藏在冰山之下,如下图所示:

简单针对上图做一个说明:当程序员在业务代码中通过 logger.info的方式对日志内容进行输出后,日志的目的地是磁盘,而在最终将日志内容刷入磁盘之前,它需要经过日志框架、JVM、Linux 文件系统的层层处理。这就好比在物流运输过程中,期间有多个经停站点,在某些站点可能还需要进行换乘。运输中用到的整个交通体系(车、司机、道路等)就是我们图中的所画的“日志通道”。根据这个图,也给出了我们进行系统性优化的思路,即:避免通道拥塞、减少看得见的业务开销、躲开看不见的系统开销。

避免通道拥塞

交通体系中,避免通道拥塞的思路主要是两个:1) 尽量控制运输流量 ,2) 优化整个交通运输体系(修更多的道路,增加更多的信息化技术等等)。在日志输出场景中,程序员能控制的主要是业务日志的内容和日志策略的配置,还有相当一部分能力依赖底层基础设施的性能。针对程序员能控制的,我们尽量优化;而对于我们无法控制的,我们尽量解耦。这是我们这一章节阐述的主要思路。

减少业务输出内容

直观来说,日志内容越大,对整个系统会造成一些更大的压力。为了量化差别,我们进行了下面的测试对比:第一组,我们仅仅将不同日志大小写入内存。第二组中,我们将不同的日志大小写入磁盘文件。

写入内存我们使用了 Log4j 中的 CountingNoOp Appender ,他的作用是在进行日志的正式输出时,仅仅对输出的日志做计数统计,这样的一种测试方式,从某种程度上能衡量出来单纯日志框架的处理效率。

<Appenders>  <CountingNoOp name="NoOp">  </CountingNoOp></Appenders>

在下图所呈现的测试结果中,我们可以看到,即使不进行刷盘的动作,写入的吞吐量随着内容的大小而明显下降

在另外一组的测试中,我们再将不同日志大小的内容写入文件,再做类似的对比,从实验结果来看我们能得出两个简单的结论:

与只写入内存的吞吐量相比,二者的吞吐量随着日志内容的变大差距越来越大。同时随着输出内容的数量变多,在磁盘场景下呈现明显的下滑趋势,随着内容的增多,呈现逐渐趋平的趋势。

具体结果如下图:

上图中的测试数据是我们从一个 IO 设备提供了 400MB/s 左右的速度中获得;在 IO 没有被用满的情况下,增加写入内容尚能提升整体的写入量,但是一旦达到设备的瓶颈。继续写入将造成写入的堆积。

不过两组数据均能得出相同的结论,即:更大的日志文本内容,只会导致更差的处理时间。类比到生活中运输的场景,如果我们要运输的货物非常大的时候,那么就需要我们的货车具备更大空间的、更强的动力,而且运输速度也会更慢。同时过重的货物会有动力失调,轮胎爆胎等风险。为了提高运输效率和健康度,就应该尽量避免运超大型的货物。从我们的日志场景出发,过大的日志会同样会在在 CPU、内存、IO 等资源上均会对系统产生不同程度的冲击。

减少系统输出内容压缩Logger输出:

在获取一个 Logger 进行日志输出时,大多数程序员的编程习惯是直接使用 Class 对象进行获取,参见如下的代码片段:

package com.alibabacloud.edas.demo;public class PoweredByEdas {  private static Logger logger = LogFactory.getLogger(        ProweredByEdas.class);    public void execute() {        String product = "EDAS";         logger.info("Prowered by " + product);    }}

而在进行日志输出时,如果 logger 是 Class 将默认输出对应它所对应的 FQCN,即:com.alibabacloud.edas.demo.PoweredByEdas

其实我们可以使用 logger 的 re-format 方式,将其进行压缩,比如,在 logback 中使用 %logger{5} 或 %c{5} 精简后,logger 在输出时将压缩成为c.a.e.d.PoweredByEdas,平均每条日志将减少 19 字符。

// 使用默认 [%logger] 进行输出2023-11-11 16:14:36.790 INFO [com.alibabacloud.edas.demo.PoweredByEdas] Prowered by EDAS // 使用默认 [%logger{5}] 进行输出2023-11-11 16:24:44.879 INFO [c.a.e.d.PoweredByEdas] Prowered by EDAS

不过这种日志处理由于做字符串的拆分和截取,会额外耗费一定的 CPU,如果是计算密集型的业务(CPU 占用本来就很高的情况下)则不建议生产使用。

压缩异常输出

异常信息的记录,是我们的系统在线上出现问题或者故障时的一个重要的排查依据,他的全面与否很多时候直接影响了问题解决的效率,然而过多的异常信息记录,往往容易把真正有用的信息进行覆盖。而当我们将系统中抛出的异常拆开来看的时候,不难看出通篇的堆栈信息中,能对自己排查问题产生帮助的信息,往往只有几行,如下图所示:

根据笔者自己的经验,在将异常直接进行打印输出之前,我们可以尝试将重新遍历异常堆栈,将信息重新整理之后再输出,具体实践可参考以下几点:

保留栈顶的几帧:栈顶往往包含的是最为关键的信息,是案发的第一现场,他的信息完整性显得尤为重要。保留业务栈帧:在 Java 语言中,大家会遵循给业务代码一个单独包名的实践,此时我们可以利用包名进行栈帧的过滤和保留操作。抽样打印全栈信息:这里可以根据具体的业务情况而定,需要将全栈信息进行随机输出的原因是有的时候可能会追踪到一些系统级别的 BUG 或想了解他的一些机制。全栈信息的输出有助于问题的追根溯源。

压缩异常不仅能带来性能上的提升,而且还能节省大量的存储空间,这里感兴趣的同学可以进一步查阅之前的一篇文章:《十行代码让日志存储降低80%》

解耦通道依赖

如果说上面提到的减少内容是把承载的货物减轻的话,那么针对通道的优化思路就是优化交通运输的整体效率;站在应用的角度上思考,通道的优化,和系统运行时的状态、以及所使用框架的实现方式有着莫大的关系,言下之意就是有着很大一部分的内容不受编程人员的控制。对于不受控制的部分,我们的思路是最大限度解耦底层的实现,具体思路是两个:

在进行日志内容写入时,通过异步缓冲区解耦业务代码到通道(从日志框架 到 JVM 到 操作系统 FileSystem)的瓶颈。在进行文件内容落盘时,通过大文件切分成小文件的方式,尽量解耦硬件级别的瓶颈。

如下图所示:

内容剩余60%,完整内容可点击下方链接查看:浅析JAVA日志中的几则性能实践与原理解释-阿里云开发者社区

阿里云开发者社区,千万开发者的选择。百万精品技术内容、千节免费系统课程、丰富的体验场景、活跃的社群活动、行业专家分享交流,尽在:阿里云开发者社区-云计算社区-阿里云

标签: #java中日志的作用 #java logger使用 #java中logger的用法