文章目录

  • 1. 什么是slf4j?
  • 2. log4j简介和配置
    • 2.1 log4j介绍
    • 2.2 log4j三大组件
    • 2.3 log4j pom依赖引入
    • 2.4 配置log4j
  • 3. logback简介和配置
    • 3.1 logback简介
    • 3.2 logback pom依赖引入
    • 3.3 logback 配置模版
    • 3.4 logback总结
  • 4. logback和log4j对比
  • 5. log4j2简介和配置
    • 5.1 log4j2简介
    • 5.2 log4j2常见组件
    • 5.3 log4j2配置文件
    • 5.4 log4j2异步日志的四种队列
  • 6.Disruptor源码解析
    • 6.1 循环缓冲区RingBuffer
    • 6.2 单生产者模型
    • 6.3 多生产者模型
    • 6.4 消费者实现
    • 6.5 消费者等待策略
  • 7. logback和log4j2对比
    • 7.1 log4j2更多的特性支持
    • 7.2 总结

1. 什么是slf4j?

slf4j是The Simple Logging Facade for Java的简称,笼统的讲就是slf4j是一系列的日志接口,这里是一个典型的门面模式的设计。slf4j,log4j和logback的作者都是Ceki Gülcü。最早开发的是log4j,后来基于log4j抽出了统一的日志接口slf4j,并基于slf4j和log4j,优化开发了logback,所以logback无论在使用还是性能方面都要优于log4j。

2. log4j简介和配置

2.1 log4j介绍

Log4j(Log for Java)是Apache的一个开源项目,通过使用Log4j,我们可以控制日志信息输送的目的地是控制台、文件、GUI组件,甚至是套接口服务器、NT的事件记录器、UNIX Syslog守护进程等;我们也可以控制每一条日志的输出格式;通过定义每一条日志信息的级别,我们能够更加细致地控制日志的生成过程。最令人感兴趣的就是,这些可以通过一个配置文件来灵活地进行配置,而不需要修改应用的代码。

2.2 log4j三大组件

  • Loggers(记录器)  主要作用控制日志输出类别;
Loggers组件在此系统中被分为五个级别:DEBUG、INFO、WARN、ERROR和FATAL。这五个级别是有顺序的, FATAL>ERROR >WARN >INFO >DEBUG 分别用来指定这条日志信息的重要程度,明白这一点很重要。Log4j级别控制规则:只输出级别不低于设定级别的日志信息,假设Loggers级别设定为INFO,则INFO、WARN、ERROR和FATAL级别的日志信息都会输出,而级别比INFO低的DEBUG则不会输出。
  • Appenders (输出源)  主要作用控制日志输出的地方,如控制台(Console)、文件(Files)等;
常使用的类如下:org.apache.log4j.ConsoleAppender(控制台)org.apache.log4j.FileAppender(文件)org.apache.log4j.DailyRollingFileAppender(每天产生一个日志文件)org.apache.log4j.RollingFileAppender(文件大小到达指定尺寸的时候产生一个新的文件)org.apache.log4j.WriterAppender(将日志信息以流格式发送到任意指定的地方)
  • Layouts(布局)  主要作用控制日志以何种形式输出;
有时用户希望根据自己的喜好格式化自己的日志输出,Log4j可以在Appenders的后面附加Layouts来完成这个功能。Layouts提供四种日志输出样式,如根据HTML样式、自由指定样式、包含日志级别与信息的样式和包含日志时间、线程、类别等信息的样式。
3.1、常使用的类如下:
org.apache.log4j.HTMLLayout(以HTML表格形式布局)
org.apache.log4j.PatternLayout(可以灵活地指定布局模式)
org.apache.log4j.SimpleLayout(包含日志信息的级别和信息字符串)
org.apache.log4j.TTCCLayout(包含日志产生的时间、线程、类别等信息)

2.3 log4j pom依赖引入

基本模式
<dependency><groupId>log4j</groupId><artifactId>log4j</artifactId><version>${log4j.version}</version>
</dependency>
下面这种模式会引入log4j,log4j-api和log4j-core<!--slf4j对应log4j2的中间件,即桥接,告诉slf4j使用log4j2--><dependency><groupId>org.apache.logging.log4j</groupId><artifactId>log4j-slf4j-impl</artifactId><version>${log4j.version}</version></dependency>

2.4 配置log4j

Log4j支持两种配置文件格式,一种是XML(标准通用标记语言下的一个应用)格式的文件,一种是Java特性文件log4j.properties(键=值)。
这里先用log4j.properties的方式,后面log4j2使用xml的方式

### set log levels ###
log4j.rootLogger = DEBUG,Console,File###  输出到控制台  ###
log4j.appender.Console=org.apache.log4j.ConsoleAppender
log4j.appender.Console.Target=System.out
log4j.appender.Console.layout=org.apache.log4j.PatternLayout
log4j.appender.Console.layout.ConversionPattern= %d{ABSOLUTE} %5p %c{1}:%L - %m%n### 输出到日志文件 ###
log4j.appender.File=org.apache.log4j.RollingFileAppender
log4j.appender.File.File=${project}/WEB-INF/logs/app.log
log4j.appender.File.DatePattern=_yyyyMMdd'.log'
log4j.appender.File.MaxFileSize=10MB
log4j.appender.File.Threshold=ALL
log4j.appender.File.layout=org.apache.log4j.PatternLayout
log4j.appender.File.layout.ConversionPattern=[%p][%d{yyyy-MM-dd HH\:mm\:ss,SSS}][%c]%m%n

3. logback简介和配置

3.1 logback简介

logback是java的日志开源组件,是log4j创始人基于log4j改造和优化的一个版本,性能比log4j要好,目前主要分为3个模块:

logback-core:核心代码模块
logback-classic:log4j的一个改良版本,同时实现了slf4j的接口,这样你如果之后要切换其他日志组件也是一件很容易的事
logback-access:访问模块与Servlet容器集成提供通过Http来访问日志的功能

3.2 logback pom依赖引入

<logback.version>1.2.3</logback.version>
<!--这个依赖直接包含了 logback-core 以及 slf4j-api的依赖-->
<dependency><groupId>ch.qos.logback</groupId><artifactId>logback-classic</artifactId><version>${logback.version}</version>
</dependency>

3.3 logback 配置模版

<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true" scanPeriod="60 seconds" debug="false"><!--设置app名称,后面日志路径使用--><property name="APP_NAME" value="metadata-manage" /><springProfile name="dev"><!--开发环境路径--><property name="LOG_HOME" value="/Users/aaa/IdeaProjects/log"/></springProfile><springProfile name="test,production"><!--测试和生产环境路径--><property name="LOG_PATH" value="/data/logs/${APP_NAME}" /></springProfile><!--配置控制台appender--><appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"><encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"><!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符--><pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern></encoder></appender><!--配置info文件appender--><appender name="INFO"  class="ch.qos.logback.core.rolling.RollingFileAppender"><file>${LOG_PATH}/info.log</file><rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"><!--日志文件输出的文件名--><FileNamePattern>${LOG_PATH}/info.%d{yyyy-MM-dd}.%i.tar.gz</FileNamePattern><!--日志文件保留天数--><maxHistory>10</maxHistory><totalSizeCap>15GB</totalSizeCap><timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP"><!--文件达到 最大128MB时会被压缩和切割 --><maxFileSize>1024MB</maxFileSize></timeBasedFileNamingAndTriggeringPolicy></rollingPolicy><encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"><pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern></encoder><filter class="ch.qos.logback.classic.filter.LevelFilter"><level>INFO</level><onMatch>ACCEPT</onMatch><onMismatch>NEUTRAL</onMismatch></filter><filter class="ch.qos.logback.classic.filter.LevelFilter"><level>DEBUG</level><onMatch>ACCEPT</onMatch><onMismatch>DENY</onMismatch></filter></appender><!--配置WARN文件appender--><appender name="WARN"  class="ch.qos.logback.core.rolling.RollingFileAppender"><file>${LOG_PATH}/warn.log</file><rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"><!--日志文件输出的文件名--><FileNamePattern>${LOG_PATH}/warn.%d{yyyy-MM-dd}.%i.tar.gz</FileNamePattern><!--日志文件保留天数--><maxHistory>10</maxHistory><totalSizeCap>10GB</totalSizeCap><timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP"><!--文件达到 最大128MB时会被压缩和切割 --><maxFileSize>1024MB</maxFileSize></timeBasedFileNamingAndTriggeringPolicy></rollingPolicy><encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"><pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern></encoder><filter class="ch.qos.logback.classic.filter.LevelFilter"><level>WARN</level><onMatch>ACCEPT</onMatch><onMismatch>DENY</onMismatch></filter></appender><!--配置ERROR文件appender--><appender name="ERROR"  class="ch.qos.logback.core.rolling.RollingFileAppender"><file>${LOG_PATH}/error.log</file><rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"><!--日志文件输出的文件名--><FileNamePattern>${LOG_PATH}/error.%d{yyyy-MM-dd}.%i.log</FileNamePattern><!--日志文件保留天数--><maxHistory>10</maxHistory><totalSizeCap>10GB</totalSizeCap><timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP"><!--文件达到 最大128MB时会被压缩和切割 --><maxFileSize>512MB</maxFileSize></timeBasedFileNamingAndTriggeringPolicy></rollingPolicy><encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"><pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern></encoder><filter class="ch.qos.logback.classic.filter.LevelFilter"><level>ERROR</level><onMatch>ACCEPT</onMatch><onMismatch>DENY</onMismatch></filter></appender><!--配置异步打印日志文件的appender--><appender name="asyncFileAppender" class="ch.qos.logback.classic.AsyncAppender"><discardingThreshold>0</discardingThreshold><queueSize>512</queueSize><appender-ref ref="INFO" /></appender><!--<logger name="com.package....." level="DEBUG"/>--><!-- 开发环境 --><springProfile name="dev,test,prod"><root level="DEBUG"><appender-ref ref="STDOUT" /></root></springProfile><!-- 生产环境 --><springProfile name="release,test"><root level="INFO"><appender-ref ref="ERROR" /><appender-ref ref="WARN" /><appender-ref ref="INFO" /></root></springProfile></configuration>

3.4 logback总结

  1. 从我的经验来看,理想的日志格式应当包括(当然除了日志信息本身了):当前时间(无日期,毫秒级精度,因为外围的文件已经按照日期来进行区分了),日志级别,线程名,简单的日志名称(不用全称)还有消息。在logback里会是这样的:
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">   <encoder>        <pattern>%d{HH:mm:ss.SSS} %-5level [%thread][%logger{0}] %m%n</pattern>   </encoder>
</appender>当然,也可以带上日期<encoder><pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern><charset>UTF-8</charset></encoder>
  1. 打印日志时使用模式注入的方式,不要通过字符串拼接;
log.debug("Found {} records matching filter: '{}'", records, filter);
错误示例
log.debug("Found " + records + " recordsmatching filter: '" + filter + "'");
  1. 尽量使用异步的方式打印日志

4. logback和log4j对比

  • 更快的执行速度: logback基于log4j的基础上开发的,重写了内部的实现,在某些特定的场景上面,甚至可以比之前的速度快上10倍。在保证logback的组件更加快速的同时,同时所需的内存更加少。
  • logback-classic 非常自然的实现了SLF4J:ogback-classic中的longging类自然的实现了SLF4J。当你使用 logback-classic作为底层实现时,涉及到LF4J日记系统的问题你完全不需要考虑。更进一步来说,由于 logback-classic强烈建议使用SLF4J作为客户端日记系统实现,如果需要切换到log4j或者其他,你只需要替换一个jar包即可,不需要去改变那些通过SLF4J API 实现的代码。这可以大大减少更换日记系统的工作量。
  • 自动重新载入配置文件:Logback-classic可以在配置文件被修改后,自动重新载入。这个扫描过程很快,无资源争用,并且可以动态扩展支持在上百个线程之间每秒上百万个调用。它和应用服务器结合良好,并且在JEE环境通用,因为它不会调用创建一个单独的线程来做扫描。
    优雅地从I/O错误中恢复:FileAppender和它的子类,包括RollingFileAppender,可以优雅的从I/O错误中恢复。所以,如果一个文件服务器临时宕机,你再也不需要重启你的应用,而日志功能就能正常工作。当文件服务器恢复工作,logback相关的appender就会透明地和快速的从上一个错误中恢复。
  • 自动清除旧的日志归档文件:通过设置TimeBasedRollingPolicy 或者 SizeAndTimeBasedFNATP的 maxHistory 属性,你就可以控制日志归档文件的最大数量。如果你的回滚策略是每月回滚的,并且你希望保存一年的日志,那么只需简单的设置maxHistory属性为12。对于12个月之前的归档日志文件将被自动清除。
  • 自动压缩归档日志文件:RollingFileAppender可以在回滚操作中,自动压缩归档日志文件。压缩通常是异步执行的,所以即使是很大的日志文件,你的应用都不会因此而被阻塞。
    配置文件中的条件处理:开发者通常需要在不同的目标环境中变换logback的配置文件,例如开发环境,测试环境和生产环境。这些配置文件大体是一样的,除了某部分会有不同。为了避免重复,logback支持配置文件中的条件处理,只需使用,和,那么同一个配置文件就可以在不同的环境中使用了。
  • 过滤: Logback拥有远比log4j更丰富的过滤能力。例如,让我们假设,有一个相当重要的商业应用部署在生产环境。考虑到大量的交易数据需要处理,记录级别被设置为WARN,那么只有警告和错误信息才会被记录。现在,想象一下,你在开发环境遇到了一个臭虫,但是在测试平台中却很难发现,因为一些环境之间(生产环境/测试环境)的未知差异。使用log4j,你只能选择在生产系统中降低记录的级别到DEBUG,来尝试发现问题。但是很不幸,这会生成大量的日志记录,让分析变得困难。更重要的是,多余的日志记录会影响到生产环境的性能。使用logback,你可以选择保留只所有用户的WARN级别的日志,而除了某个用户,例如Alice,而她就是问题的相关用户。当Alice登录系统,她就会以DEBUG级别被记录,而其他用户仍然是以WARN级别来记录日志。这个功能,可以通过在配置文件的XML中添加4行。请在相关章节中查找MDCFilter
  • logback原生支持同时按日期和文件大小分割日志,而log4j需要自己写代码实现

5. log4j2简介和配置

5.1 log4j2简介

logbakc是站在log4j的基础上开发设计的,而Log4j2则是站在logback这个巨人肩膀上升级优化的,虽然在各个方面都与logback非常相似,但是却提供了更强的性能和并发性,尤其在异步logget这块。

5.2 log4j2常见组件

  • LoggerContext:日志系统上下文;
  • Configuration:每一个 LoggerContext 都有一个有效的 Configuration, Configuration 包含所有的Appender 、Filter、LoggerConfig ,StrSubstitutor引用和对Layout的格式设置;
  • Logger : Logger继承自 AbstractLogger,当配置被修改后,它将与不同的 LoggerConfig 相关联,这导致其行为也被改变;
  • LoggerConfig:LoggerConfig 对象在 Logger 被声明时创建,它包含了一组用于处理事件的Appender引用,以及一组用于过滤传递给Appender事件的Filter,相当于是Appender的集合;
  • Appender:Log4j2 还允许将记录请求输出到多个目标中,而这种输出目标被称为Appender。目前Appender的类型有控制台、文件、socket、Apache Flume、JMS、远程UNIX 系统日志守护进程以及各种数据库API,用户可以根据需要选择将日志输出到不同的目标上,同时在一个Logger的配置中,允许开启多个Appender;
  • Filter :Log4j2 提供了Filter 来过滤消息事件,它可被应用于事件传递给LoggerConfig之前,及传递给LoggerConfig之后,即LoggerConfig的前后置拦截器。Filter包含了三种行为: Accept, Deny 或 Neutral,其中Accept,Deny分别代表着接受和拒绝,即过滤器接受或拒绝某种日志过滤表达式等,经过这两种行为处理后将不再经过其他过滤器。Neutral代表着中立,意味着事件应由其他Filter来处理。如果未配置任何Filter,那么事件将直接被处理;
  • Layout:Log4j2除了可以输出到不同的目标Appender之外,还支持在目标中定义自定义的日志格式,Layout 负责对日志事件进行格式化,通过配置PatternLayout来实现;
  • Policy是用来控制日志文件何时(When)进行滚动的;Strategy是用来控制日志文件如何(How)进行滚动的。如果配置的是RollingFile或RollingRandomAccessFile,则必须配置一个Policy;

5.3 log4j2配置文件

<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="false"><!--定义日志文件的存储地址 勿在 LogBack 的配置中使用相对路径--><property name="LOG_HOME" value="${app.log.root}"/><!-- 控制台输出 --><appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"><encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"><!--格式化输出,%d:日期;%thread:线程名;%-5level:级别,从左显示5个字符宽度;%msg:日志消息;%n:换行符--><pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern></encoder></appender><appender name="ERROR" class="ch.qos.logback.core.ConsoleAppender"><!-- filter过滤需要输出的类型或者级别 --><filter class="ch.qos.logback.classic.filter.LevelFilter"><level>ERROR</level><onMatch>ACCEPT</onMatch><onMismatch>DENY</onMismatch></filter><!-- 格式化方式 --><encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"><!--格式化输出,%d:日期;%thread:线程名;%-5level:级别,从左显示5个字符宽度;%msg:日志消息;%n:换行符--><pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern></encoder></appender><!-- 按照每天生成日志文件 --><appender name="SYS" class="ch.qos.logback.core.rolling.RollingFileAppender"><file>${LOG_HOME}/sys.log</file><rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy"><!-- rollover daily --><fileNamePattern>${LOG_HOME}/sys-%d{yyyy-MM-dd}.%i.log</fileNamePattern><!-- each file should be at most 100MB, keep 60 days worth of history, but at most 20GB --><maxFileSize>20MB</maxFileSize><maxHistory>60</maxHistory><totalSizeCap>2GB</totalSizeCap></rollingPolicy><encoder><pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern><charset>UTF-8</charset></encoder></appender><appender name="APP" class="ch.qos.logback.core.rolling.RollingFileAppender"><file>${LOG_HOME}/app.log</file><rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy"><!-- rollover daily --><fileNamePattern>${LOG_HOME}/app-%d{yyyy-MM-dd}.%i.log</fileNamePattern><!-- each file should be at most 100MB, keep 60 days worth of history, but at most 20GB --><maxFileSize>20MB</maxFileSize><maxHistory>60</maxHistory><totalSizeCap>2GB</totalSizeCap></rollingPolicy><encoder><pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern><charset>UTF-8</charset></encoder></appender><appender name="MONITOR" class="ch.qos.logback.core.rolling.RollingFileAppender"><file>${LOG_HOME}/monitor.log</file><rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy"><!-- rollover daily --><fileNamePattern>${LOG_HOME}/monitor-%d{yyyy-MM-dd}.%i.log</fileNamePattern><!-- each file should be at most 100MB, keep 60 days worth of history, but at most 20GB --><maxFileSize>20MB</maxFileSize><maxHistory>60</maxHistory><totalSizeCap>2GB</totalSizeCap></rollingPolicy><encoder><pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern><charset>UTF-8</charset></encoder></appender><appender name="DYEING" class="ch.qos.logback.core.rolling.RollingFileAppender"><file>${LOG_HOME}/dyeing.log</file><rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy"><!-- rollover daily --><fileNamePattern>${LOG_HOME}/dyeing-%d{yyyy-MM-dd}.%i.log</fileNamePattern><!-- each file should be at most 100MB, keep 60 days worth of history, but at most 20GB --><maxFileSize>20MB</maxFileSize><maxHistory>60</maxHistory><totalSizeCap>2GB</totalSizeCap></rollingPolicy><encoder><pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level - %msg%n</pattern><charset>UTF-8</charset></encoder></appender><appender name="ASYNC_DYEING" class="ch.qos.logback.classic.AsyncAppender"><appender-ref ref="DYEING"/></appender><logger name="dyeingLogger" level="INFO"><appender-ref ref="ASYNC_DYEING"/></logger><!--给具体的包绑定appender --><logger name="com.package.aaa" level="INFO"><appender-ref ref="APP"/></logger><!-- 日志输出级别 --><root level="INFO"><appender-ref ref="ERROR"/></root>
</configuration>

5.4 log4j2异步日志的四种队列

ArrayBlockingQueue -- 默认的队列,通过 java 原生的 ArrayBlockingQueue 实现。
DisruptorBlockingQueue -- disruptor 包实现的高性能队列。
JCToolsBlockingQueue -- JCTools 实现的无锁队列。
LinkedTransferQueue -- 通过 java7 以上原生支持的 LinkedTransferQueue 实现。

6.Disruptor源码解析

6.1 循环缓冲区RingBuffer

讲解mysql的文章中,有说到mysql的redolog 是通过一个环形的存储区域实现其循环写入的,以保障性能和一致性。

在 linux 内核中,进程间通信所使用的 fifo 也是通过环形存储区域来实现的。

RingBuffer的好处:

  1. 基于数组实现,内存被循环使用,减少了内存分配、回收扩容等操作。
  2. 对于只有单个读取和写入进程的场景下,读取写入分别在环的不同位置进行,因此,读写过程无需加锁,从而能够让缓存的读写更为高效。

disruptor 正是借鉴这一思想,使用环形队列实现缓冲,由于 RingBuffer 实现了空间的循环利用,一次开辟,即可一直驻留在内存中,降低了 GC 的压力,从而提升缓冲的性能。同时,disruptor 基于RingBuffer,提供了单生产者、多生产者、单消费者、多消费者组等多种模型供不同的场景中可以灵活使用,在这些模式下,disruptor 尽量通过 Unsafe 包中的 CAS 操作结合自旋的方式避免了锁的使用,从而让整个实现十分简洁而高效。

6.2 单生产者模型

相对于多生产者模型而言,单生产者模型显然更为简单,我们来看看他是怎么实现的:

// Disruptor
public <A> void publishEvent(final EventTranslatorOneArg<T, A> eventTranslator, final A arg) {ringBuffer.publishEvent(eventTranslator, arg);
}// RingBuffer
public <A> void publishEvent(EventTranslatorOneArg<E, A> translator, A arg0) {long sequence = this.sequencer.next();this.translateAndPublish(translator, sequence, arg0);
}public long next() {return this.next(1);
}public long next(int n) {if (n < 1) {throw new IllegalArgumentException("n must be > 0");} else {// 获取上次数据写入位置long nextValue = this.pad.nextValue;// 获取本次数据写入位置long nextSequence = nextValue + (long)n;// 计算成环点long wrapPoint = nextSequence - (long)this.bufferSize;// 消费者下次消费位置long cachedGatingSequence = this.pad.cachedValue;// 缓存位置不足,自旋等待if (wrapPoint > cachedGatingSequence || cachedGatingSequence > nextValue) {long minSequence;while(wrapPoint > (minSequence = Util.getMinimumSequence(this.gatingSequences, nextValue))) {Thread.yield();}// 被唤醒说明有消费者消费,更新消费位置this.pad.cachedValue = minSequence;}// 获取数据插入位置并返回this.pad.nextValue = nextSequence;return nextSequence;}
}

可以看到,整个获取写入位置的代码并不复杂,即在 RingBuffer 中获取写入位置,如果 RingBuffer 空间不足,则调用 yield 等待 consumer 唤醒,一旦位置充足,则返回写入位置,之后,调用 translateAndPublish 方法发布数据。

6.3 多生产者模型

多生产者模型下,disruptor 通过对不同生产者进行隔离实现了生产过程的无冲突,也就是说,每个生产者只能对 RingBuffer 上分配给自己的独立空间进行写入,但这样一来,就引入了一个新的问题,由于 RingBuffer 不再是连续的,consumer 怎么知道到哪里去获取数据呢?解决方法也很简单,disruptor 引入了一个额外的缓冲区 availableBuffer,他的长度与 RingBuffer 长度相同,因此,他的槽位与 RingBuffer 的槽位一一对应,一旦有数据写入就在 availableBuffer 的对应位置置1,消费后则置 0,从而让读取的时候明确获知下一位置。

availableBuffer 在使用中,虽然被多个生产者划分为多个区域,实际上,每个生产者在操作自己所持有的 availableBuffer 片段时,也是将这个片段作为一个 RingBuffer 来使用,这样巧妙地转化,便让多生产模型完全可以复用单生产者模型中的实现,因此在多生产者模型下,写入流程与单生产者模型并无太大区别,仅在 next 方法与 publish 方法的实现上有所区别:

public long next(int n) {if (n < 1) {throw new IllegalArgumentException("n must be > 0");} else {long current;long next;do {while(true) {// 获取下一写入位置current = this.cursor.get();next = current + (long)n;// 计算持有的 availableBuffer 片段的城环段long wrapPoint = next - (long)this.bufferSize;// 计算下一消费位置long cachedGatingSequence = this.gatingSequenceCache.get();if (wrapPoint <= cachedGatingSequence && cachedGatingSequence <= current) {break;}// 自旋等待long gatingSequence = Util.getMinimumSequence(this.gatingSequences, current);if (wrapPoint > gatingSequence) {LockSupport.parkNanos(1L);} else {this.gatingSequenceCache.set(gatingSequence);}}} while(!this.cursor.compareAndSet(current, next));return next;}
}

多生产者模型下,通过自旋与缓存切片的方式,成功避免了锁的使用,实现了高效的生产操作。

6.4 消费者实现

EventProcessor 是整个消费者事件处理框架,EventProcessor 接口继承了 Runnable 接口,主要有两种实现:
单线程批量处理 BatchEventProcessor
多线程处理 WorkProcessor

针对单消费者和多消费者,实现模式区别:
广播模式 – 使用 handleEventsWith 方法传入多个 EventHandler,内部使用多个 BatchEventProcessor 关联多个线程执行,是典型的发布订阅模式,同一事件会被多个消费者并行消费,适用于同一事件触发多种操作。每个 BatchEventProcessor 是单线程的任务链,任务执行有序且非常快。
集群消费模式 – 使用 handleEventsWithWorkerPool 方法传入多个WorkHandler时,内部使用多个 WorkProcessor 关联多个线程执行,类似于 JMS 的点对点模式,同一事件会被一组消费者其中之一消费,适用于提升消费者并行处理能力,每个 WorkProcessor 内部实现是多线程的,无法保证任务执行的顺序

6.5 消费者等待策略

BlockingWaitStrategy:这是默认的策略。使用锁和条件进行数据的监控和线程的唤醒。因为涉及到线程的切换,是最节省CPU,但在高并发下性能表现最糟糕的一种等待策略。
SleepingWaitStrategy:会自旋等待数据,如果不成功,才让出cpu,最终进行线程休眠,以确保不占用太多的CPU数据,因此可能产生比较高的平均延时。比较适合对延时要求不高的场合,好处是对生产者线程的影响最小。典型的应用场景是异步日志。
YieldingWaitStrategy:用于低延时的场合。消费者线程不断循环监控缓冲区变化,在循环内部,会使用Thread.yield()让出cpu给别的线程执行时间。
BusySpinWaitStrategy:开启的是一个死循环监控,消费者线程会尽最大努力监控缓冲区变化,因此,CPU负担比较大

7. logback和log4j2对比

7.1 log4j2更多的特性支持

  1. 丢数据这种情况少,可以用来做审计功能。而且自身内部报的exception会被发现,但是logback和log4j不会;
  2. log4j2使用了disruptor技术,在多线程环境下,性能高于logback等10倍以上;
  3. (garbage free)之前的版本会产生非常多的临时对象,会造成GC频繁,log4j2则在这方面上做了优化,减少产生临时对象。尽可能少的GC,其实也是基于disruptor技术;
  4. 支持lambda表达式;
  5. 对filter的功能支持的更强大;
  6. 系统日志(Syslog)协议支持TCP 和 UDP
  7. 支持kafka queue

7.2 总结

  1. 在线上环境关闭控制台输出会对性能有所提升;
  2. 相同情况下对比,log4j2性能更优;
  3. 在对日志输出场景比较多的情况下可以考虑将logback更换为log4j2;

常见日志框架介绍和对比(log4j,logback,log4j2)相关推荐

  1. 【SpringBoot】Logback日志框架介绍和SpringBoot整合实战

    ========================11.Logback日志框架介绍和SpringBoot整合实战 2节课================================ 1.新日志框架L ...

  2. Java常用日志框架介绍

    Java常用日志框架介绍 文章目录 Java常用日志框架介绍 日志接口和日志框架的区别 各个框架出现的时间线 桥接包 Log4j2 常用搭配 单独的log4j 使用JCL(commons-loggin ...

  3. Java日志框架 -- 日志框架介绍、日志门面技术、JUL日志(JUL架构、JUL入门示例、JUL日志级别、JUL日志的配置文件)

    1. 日志的概念 日志文件是用于记录系统操作事件的文件集合,可分为事件日志和消息日志.具有处理历史数据.诊断问题的追踪以及理解系统的活动等重要作用. 2. Java日志框架 问题: 控制日志输出的内容 ...

  4. 常见充值方式介绍及对比 (转)

    常见充值方式介绍及对比 1:银联充值 1:环境部署 安装NetPay4NTSetup.exe,将MerPrk.key和PgPubk.key两个文件放到C:\WINDOWS目录下,环境部署完成了. 2: ...

  5. 日志框架-1 jul与log4j与logback介绍

    背景 日志框架一直没进行过细致的梳理,对其理解仅留在基本使用的程度:周五自测小功能时暴露了技术漏洞

  6. 日志框架介绍(SLF4J及其使用)

    本文主要介绍 1.日志框架基本介绍及Spring Boot中默认使用框架(SLF4J+LogBack); 2.SLF4J的使用 3.项目中日志框架统一问题 4.Spring Boot中如何实现日志框架 ...

  7. Java日志框架介绍

    一.序言 日志为系统的必不可少的一部分,通过输出的日志我们可以排查线上出现的各种问题,就像断案的线索一样.我们还可以通过日志数据分析用户的行为习惯做大数据分析. 二.日志框架分类及其历史 框架的种类: ...

  8. 主流webgis框架介绍与对比

    概述 想写本文,主要是源于前两天有个老师找到我说让我录一个大概半个小时的视频,跟大家分享一下各webgis框架之间的区别以及在应用的过程中应该如何选择.其实之前也有学员问过类似的问题,当时只是针对他们 ...

  9. Log4j日志框架介绍

    日志是应用软件中不可缺少的部分,Apache的开源项目log4j是一个功能强大的日志组件,提供方便的日志记录.在apache网站:jakarta.apache.org/log4j 可以免费下载到Log ...

最新文章

  1. 轻松实现SQL Server异地备份
  2. 双向多点路由重分布--如何防止路由环路以及次优路径
  3. [BZOJ2125]最短路(圆方树DP)
  4. 图论中的基础概念总结
  5. mysql数据库语句分类_细数MySQL中SQL语句的分类
  6. nano java_java – System.nanoTime()完全无用?
  7. 云服务器被攻击了怎么办
  8. 前沿重器[26] | 预训练模型的领域适配问题
  9. 删除Mac右上角可恶的状态栏图标
  10. 关于Qt bindValue函数出错问题
  11. linux php 编程 pdf 百度云,Linux下的Perl编程 pdf版
  12. 如何修复dns服务器超时,DNS服务器安全及解析超时问题的解决
  13. Windows通用克隆系统入门基础知识简介
  14. java使用knn实现mnist_java使用knn实现mnist - 百度学术
  15. 校招总结—FPGA从入门到放弃
  16. NBA表格_1970到2019年NBA各赛季总冠军获得球队汇总!让你印象深刻的赛季
  17. windows映射网络驱动器出错解决方案
  18. 记录下今天的搜索成果
  19. IEEE PHM 2012挑战赛的轴承数据集及预处理程序(MATLAB代码)
  20. 机器人开发学习(一)

热门文章

  1. 电力电子技术(第一章习题)
  2. 【Super Resolution】超分辨率——SRCNN
  3. 我遇见了一个问题求帮助
  4. 杰理AC692X---在线EQ调试
  5. 基于javaweb的出租车管理系统(java+ssm+html+javascript+jsp+mysql)
  6. excel2013导入SQL
  7. php文件更名,怎么用php给文件改名
  8. 关于征订2009年“注册电气工程师执业资格考试(供配电专业)
  9. visual studio 2010 怎么显示行号
  10. 大规模分布式应用储能技术