你完全没了解过的日志异步落库

在互联网设计架构过程中,日志异步落库,俨然已经是高并发环节中不可缺少的一环。为什么说是高并发环节中不可缺少的呢? 原因在于,如果直接用mq进行日志落库的时候,低并发下,生产端生产数据,然后由消费端异步落库,是没有什么问题的,而且性能也都是异常的好,估计tp99应该都在1ms以内。但是一旦并发增长起来,慢慢的你就发现生产端的tp99一直在增长,从1ms,变为2ms,4ms,直至send timeout。尤其在大促的时候,我司的系统就经历过这个情况,当时mq的发送耗时超过200ms,甚至一度有不少timeout产生。

考虑到这种情况在高并发的情况下才出现,所以今天我们就来探索更加可靠的方法来进行异步日志落库,保证所使用的方式不会因为过高的并发而出现接口ops持续下降甚至到不可用的情况。

方案一: 基于log4j的异步appender实现

此种方案,依赖于log4j。在log4j的异步appender中,通过mq进行生产消费入库。相当于在接口和mq之间建立了一个缓冲区,使得接口和mq的依赖分离,从而不让mq的操作影响接口的ops。

此种方案由于使用了异步方式,且由于异步的discard policy策略,当大量数据过来,缓冲区满了之后,会抛弃部分数据。此种方案适用于能够容忍数据丢失的业务场景,不适用于对数据完整有严格要求的业务场景。

来看看具体的实现方式:

首先,我们需要自定义一个Appender,继承自log4j的AppenderSkeleton类,实现方式如下:

public class AsyncJmqAppender extends AppenderSkeleton {     @Resource(name = "messageProducer")     private MessageProducer messageProducer;     @Override    protected void append(LoggingEvent loggingEvent) {         asyncPushMessage(loggingEvent.getMessage());     }     /**      * 异步调用jmq输出日志      * @param message      */    private void asyncPushMessage(Object message) {         CompletableFuture.runAsync(() -> {             Message messageConverted = (Message) message;             try {                 messageProducer.send(messageConverted);             } catch (JMQException e) {                 e.printStackTrace();             }         });     }     @Override    public boolean requiresLayout() {         return false;     }     @Override    public void close() {     } }

然后在log4j.xml中,为此类进行配置:

<!--异步JMQ appender--><appender name="async_mq_appender" class="com.jd.limitbuy.common.util.AsyncJmqAppender">    <!-- 设置File参数:日志输出文件名 -->    <param name="File" value="D:/export/Instances/order/server1/logs/order.async.jmq" />    <!-- 设置是否在重新启动服务时,在原有日志的基础添加新日志 -->    <param name="Append" value="true" />    <!-- 设置文件大小 -->    <param name="MaxFileSize" value="10KB" />    <!-- 设置文件备份 -->    <param name="MaxBackupIndex" value="10000" />    <!-- 设置输出文件项目和格式 -->    <layout class="org.apache.log4j.PatternLayout">        <param name="ConversionPattern" value="%m%n" />    </layout></appender><logger name="async_mq_appender_logger">    <appender-ref ref="async_mq_appender"/></logger>

最后就可以按照如下的方式进行正常使用了:

private static Logger logger = LoggerFactory.getLogger("filelog_appender_logger");

郑重声明:本文版权归原作者所有,转载文章仅为传播更多信息之目的,如作者信息标记有误,请第一时间联系我们修改或删除,多谢。