logback如何按线程打印日志

背景

  在一次项目的性能调优中,发现出现竞争瓶颈,导致在资源未使用满的情况下,TPS已经无法提升。祭起JMC(JAVA MISSON CONTROL)飞行记录器大法后,发现线程集中等待在logback写日志的地方,如下图所示:

  由于项目组多线程写如同一个文件日志,导致存在IO竞争,一般解决这种问题有三种选择方式

  • 异步日志,但是会存在断电或者日志队列溢出丢失的可能
  • 远程日志,日志放入外部消息队列,保证持久化,但需额外部署日志存储队列
  • 多线程日志,按线程(或线程取模)记录日志,减少竞争,日志也能保证持久化

  项目组权衡再三,决定采用第三种分线程日志的方式解决。

误入SiftingAppender大坑

  项目组使用logback作为日志组件,loback是否有自动分线程写日志的功能呢?网上搜索logack multiThread 的第一篇文章就是教你如何使用SiftingAppender来分线程记录日志如下:
https://dzone.com/articles/si...
  SiftingAppender是logback根据mdc中的变量动态创建appender的代理,只要我们将一个线程号作为日志名分发器discriminator注入到SiftingAppender中,它就可以动态的为我们创建不同的appender,达到分线程的目的,配置方式举例如下:

  • 配置discriminator
public class ThreadDiscriminator extends ContextBasedDiscriminator {
    String KEY ="threadName";
    /**
     * Return the name of the current context name as found in the logging event.
     */
    public String getDiscriminatingValue(ILoggingEvent event) {
        return event.getThreadName();
    }

    public String getDefaultValue() {
        return KEY;
    }

    public String getKey() {
        return KEY;
    }

    public void setKey(String key) {
        this.KEY = key;
    }
}
  • 配置logback appender
    <!-- 分线程输出源 -->
    <appender name="frameworkthread" class="ch.qos.logback.classic.sift.SiftingAppender">
        <discriminator class="ThreadDiscriminator">
            <key>threadName</key>
        </discriminator>
        <sift>
            <appender name="FILE-${threadName}" class="ch.qos.logback.core.rolling.RollingFileAppender">
                <encoder>
                    <Encoding>UTF-8</Encoding>
                    <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS}[%c][%thread][%X{tradeNo}][%p]-%m%n</pattern>
                </encoder>
                <rollingPolicy
                    class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
                    <fileNamePattern>D:/test/threadlogs/${threadName}-%d{yyyy-MM-dd}.%i.log
                    </fileNamePattern>
                    <maxFileSize>100MB</maxFileSize>
                    <maxHistory>60</maxHistory>
                    <totalSizeCap>20GB</totalSizeCap>
                </rollingPolicy>
            </appender>
        </sift>
    </appender> 

  配置后查看输出结果也完全正确,网上的方法非常靠谱,真是piece of cake!但是接下来的性能测试让我再次懵逼:性能没有任何提升!反而更加糟糕了!这是怎么一回事呢?继续jmc查看线程状态:

  这次写文件outputStream的IO等待竟然提升到了AppenderBase.doAppender方法级别!查看AppenderBase.doAppender实现,这个方法是Synchronized!这个父类的方法是SiftingAppender的入口方法,这意味着在获取/创建线程自己真正的Appender和写入日志之前都必须排队阻塞在这个方法上!阻塞的级别提升了,当然性能更糟糕了!

优化logback:增加无需同步的SiftAppender

  logback Appender有两个最基础的抽象类,一个是同步AppenderBase,一个是不同步的UnsynchronizedAppenderBase,这两个类功能一样,只是doAppender方法的同步策略不一样(Synchronize VS ThreadLocal)。那么SiftingAppender为什么继承了AppenderBase而不是UnsynchronizedAppenderBase呢?分析原因应该是SiftingAppender作为Appender的代理集合,它即可能包含了继承自UnsynchronizedAppenderBase的OutputStreamAppender(FileAppender的基类,自行实现底层同步,doAppend方法未同步),也可能包含了继承AppenderBase的SocketAppender类(doAppend方法同步),为防止出现线程安全问题,它直接在自身的doAppend方法上进行了同步。
  在项目组实际使用时,项目组只需要分线程写文件日志,这意味这它使用的最终FileAppender应该是线程安全,完全独立的。故我们尝试在logback新增继承于UnsynchronizedAppenderBase的ParrelSiftingAppender步骤如下:

  • logback core中增加继承UnsynchronizedAppenderBase的UnsynchronizedSiftingAppenderBase
  • logback classic中增加继承UnsynchronizedSiftingAppenderBase的ParrelSiftingAppender
  • logback classic中SiftAction注册增加ParrelSiftingAppender的工厂注册

  修改完成后测试发现果然性能提升了5倍左右,CPU资源利用率接近饱和,应该基本达到效果了,JMC分析应该是没有竞争了把,但是发现新的竞争方法出现了:

Appender<E> appender = appenderTracker.getOrCreate(discriminatingValue, timestamp);

最终改进:使用ConcurrentHashMap替换LinkedHashMap

  原来在SiftingAppender内部使用了LinkedHashMap作为LRU Cache来管理Appender,会定期移除。由于LinkedHashMap不是线程安全的,故在getOrCreate方法上增加了Synchronized同步操作,造成竞争等待。
  结合业务场景,这里完全可以使用ConcurrentHashMap作为Appender的缓存,ConcurrentHashMap读写锁分离,并且key值分区上锁,在多读少写的情况下,有很高的并发性能。并且真正生成的日志Appender也并不多(100个左右),定时清理完全也不会出现内存溢出问题。
  开始动手修改appenderTracker步骤如下:

  • logback core中增加AbstractConcurrentMapComponentTracker
  • logback core中增加ConcurrentMapAppenderTracker继承

AbstractConcurrentMapComponentTracker

  • 修改UnsynchronizedSiftingAppenderBase使用ConcurrentMapAppenderTracker进行Appender管理

  最终性能测试结果:在资源相同的情况下,优化后比使用LinkedHashMap性能提高一倍。至此,整个logback多线程调优结束,通过充分优化同步竞争的方式,最终使得分线程记录日志的性能比最原始的多线程写同一文件提高了10倍(SiftAppender去锁提高到5倍,Map替换提高2倍)!

转载:https://segmentfault.com/a/1190000016204970

原文地址:https://www.cnblogs.com/minikobe/p/12161369.html

时间: 2024-11-12 02:59:06

logback如何按线程打印日志的相关文章

springboot aop + logback + 统一异常处理 打印日志

1.src/resources路径下新建logback.xml 控制台彩色日志打印 info日志和异常日志分不同文件存储 每天自动生成日志 结合myibatis方便日志打印(debug模式) <?xml version="1.0" encoding="UTF-8"?> <configuration debug="false"> <!--定义日志文件的存储地址 可以在LogBack 的配置中使用相对路径--> &

logback的使用和logback.xml详解,在Spring项目中使用log打印日志

logback的使用和logback.xml详解 一.logback的介绍 Logback是由log4j创始人设计的另一个开源日志组件,官方网站: http://logback.qos.ch.它当前分为下面下个模块: logback-core:其它两个模块的基础模块 logback-classic:它是log4j的一个改良版本,同时它完整实现了slf4j API使你可以很方便地更换成其它日志系统如log4j或JDK14 Logging logback-access:访问模块与Servlet容器集

Logback 整合 RabbitMQ 实现统一日志输出

原文地址:Logback 整合 RabbitMQ 实现统一日志输出 博客地址:http://www.extlight.com 一.前言 公司项目做了集群实现请求分流,由于线上或多或少会出现请求失败或系统异常,为了查看失败请求的日志信息,我们得将所有服务的日志文件都打开来进行问题的定位分析,操作起来非常麻烦.因此,我们开发组决定设计一套日志查看系统来解决上述问题. 二.实现思路 默认的,应用服务日志信息会保存在本地服务器的目录中,为了方便查看日志我们应该把多台服务器日志统一输出到一个日志文件中.

因打印日志而引发的故障

问题描述: 最近已经有两个项目因为日志打印问题而引发了故障,可以说是血的教训了.两次故障的原因也是非常的相似,都是由于其他业务系统调用了另外一个老系统的接口,但是由于传递的参数不正确,而老系统会因为参数不正确而打印日志.当错误的请求量增大,打印日志会造成当前线程阻塞,容易使机器机器负载升高,产生性能问题 排查方法: 1. 直接查看机器上日志大小 2. 还可以通过Jstack查看 占用cpu最多的线程,多查看几次,如果每次都是打印日志的线程,那基本上也可以确定是打印日志的问题 解决方案: 1. 提

aop打印日志

首先在资源目录下创建logback.xml文件 文件的内容 <?xml version="1.0" encoding="UTF-8"?> <configuration> <!--======================================= 本地变量 ======================================== --> <!--在没有定义${LOG_HOME}系统变量的时候,可以设置此本地

打印日志

=============================================== #最普通的log打印 import logging log_file="/tmp/mylog.log" logging.basicConfig(filename=log_file,level=logging.DEBUG) def fun1(): logging.debug("This is running fun1") if __name__=="__main_

dubbo 获取application和ip 打印日志,以便排查问题.

(1) 服务消费方 dubbo 获取application和ip 打印日志,以便排查问题.. 让对方有针对的去具体某个机器上去排查. xxxService.xxx(); // 远程调用 boolean isConsumerSide = RpcContext.getContext().isConsumerSide(); // 本端是否为消费端,这里会返回true String serverIP = RpcContext.getContext().getRemoteHost(); // 获取当前线程

Log4j配置的经典总结,打印日志文件,日志存库

    一.介绍 Log4j是Apache的一个开放源代码项目,通过使用Log4j,我们可以控制 日志信息输送的目的地是控制台.文件.GUI组件.甚至是套接口服务 器.NT的事件记录器.UNIX Syslog守护进程等:我们也可以控制每一条日志的输出格式:通过定义每一条日志信息的级别,我们能够更加细致地控制日志的生成过程. Log4j 由三个重要的组件构成:日志信息的优先级,日志信息的输出目的地,日志信息的输出格式.日志信息的优先级从高到低有ERROR.WARN. INFO.DEBUG,分别用来

boost.log(一)打印日志

打印输出 对于那些不想阅读手册,只需要一个简单的工具用于日志记录的人.你可以直接在控制台中输出日志信息,首先你需要包含头文件boost/log/trivial.hpp,然后编写下面的代码: #include <iostream> #include <boost/log/trivial.hpp> int main(int, char*[]) { BOOST_LOG_TRIVIAL(trace) << "A trace severity message"