采用logback提供的文件写入方式,对每秒十万左右的数据进行文本快速写入

笔者参与过某线上项目,该项目的数据存储量巨大,大概每秒都有10W左右的数据进行推送过来,项目最初采用的是mysql作为存储,但是以传统型的数据库,这种数据量支撑不了太久,采用分库分表也只是临时方案,最终leader决定用Hadoop + HBase 进行数据存储,但是上面担心某天大数据平台面临崩溃,所以必须同时写入一份文件到txt中,这样作为最后的保障,日后极端问题出现恢复的基础。

这种级别的数据,写入到文件当中,必定对IO有很高的要求,当时代码开发是另一位同事,采用的是Java提供的基本文件API FileOutputStream 这种,去对文件进行写入,这种方式在正常写入确实没什么问题,但是数据量太大的情况,写入速度极慢,导致MQ中的数据一直不停的挤压阻塞。

当时项目组在讨论用什么样的方式,可以快速的将文件写入到txt中,笔者经过思考,发现我们项目的日志,不论访问多频繁,流量有多大,日志总能实时的写入到日志文件中去,日志框架正是SpringBoot默认支持的logback,所以笔者灵机一动,将采用此种方式的方案,提出到上面,经过讨论后,决定采用logback提供的API,对数据写入到txt中,我先简介一下数据流动流程 和 写入过程

client data -> netty -> rabbit mq -> logback -> txt

也就是客户端将数据通过长连接的方式,发送到服务端的netty中,netty接收到数据后,存入的MQ中,我的logback程序作为订阅者,读到数据后,进行分类写入txt下面看下代码实现方式,(只挂重点写入txt的代码,很多地方笔者做了删减,读者知道意思即可)

@Component
@RabbitListener
public class DataListener {

    private static final Logger logger = LoggerFactory.getLogger(DataListener.class);

    @RabbitHandler
    public void getMessage(@Payload Data data) {
        try {
            data.setUuid(UUID.randomUUID().toString().replace("-", ""));
            // 此处根据业务逻辑以数据的num后四位作为文件名称
            String str4id = "0000";
            if (data.getNum() != null && data.getNum().length() > 4) {
                str4id = data.getNum().substring(data.getNum().length() - 4, data.getNum().length());
            }
            data.setStr4id(10000 + Long.parseLong(str4id));
            String fileName = "data_" + data.getStr4id();
            // 文件名称可以看做是一个客户端对象,这个客户端对象会不停的发送数据过来, 对每个文件名称都做了一个DataLogWriter对象的缓存,有就使用已经创建的写入,没有则创建并保存到map缓存中后写入
            DataLogWriter dataSqlWriter = dataUtil.objMap.get(fileName);
            if (dataSqlWriter == null) {
                dataSqlWriter = new dataLogWriter(fileName);
                dataUtil.objMap.put(fileName, dataSqlWriter);
            }
            dataSqlWriter.outLog(dataUtil.getdataLog(data));
        } catch (Exception ex) {
            ex.printStackTrace();
            logger.error("gpswriter异常:", ex);
        }
    }

}

package cn.net.leadu.log;

import ch.qos.logback.classic.Logger;
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.encoder.PatternLayoutEncoder;
import ch.qos.logback.core.FileAppender;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

public class DataLogWriter {

    private final Logger logbackLogger;
    LoggerContext loggerContext = new LoggerContext();
    public FileAppender fileAppender = new FileAppender();
    private String dayDate ; //当前天
    private String loggerName; //日志文件名称

    public void outLog(String msg){
        SimpleDateFormat yMd = new SimpleDateFormat("yyyy-MM-dd");
        Date date = new Date();
        String nowDate = yMd.format(date);
        //每次写入的时候,查看当前时间和对象中的天数是否相同,如果不相同则重置日志路径
        if(!nowDate.equals(dayDate)) {
            dayDate = nowDate;
            fileAppender.setFile("/data/data_backup/" + dayDate + "/" + loggerName + ".txt");
            fileAppender.start();
            logbackLogger.debug(msg);
        }else{
            logbackLogger.debug(msg);
        }
    }


    public DataLogWriter(String logfileName) {
        SimpleDateFormat yMd = new SimpleDateFormat("yyyy-MM-dd");
        Date date = new Date();
        loggerName = logfileName;
        fileAppender.setContext(loggerContext);
        fileAppender.setName("logfile");
        //文件路径 按照天 / 小时 / num后四位分文件
        dayDate = yMd.format(date);
        fileAppender.setFile("/data/data_backup/" + dayDate +"/" + loggerName + ".txt");
        PatternLayoutEncoder encoder = new PatternLayoutEncoder();
        encoder.setContext(loggerContext);
        encoder.setPattern("%msg");
        encoder.start();
        fileAppender.setEncoder(encoder);
        fileAppender.start();
        logbackLogger = loggerContext.getLogger("nelogger");
        logbackLogger.addAppender(fileAppender);
        logbackLogger.isAdditive();
    }

}

当时按照这么写后,上服务,开集群~ 发现不行… MQ依然堆积如山还是下不去?
为啥… 笔者思考一番后,发现原因是因为多个服务同时对一个文件写入时,会发现文件占用,相互等待,死锁问题,导致MQ的消息无法被读取写入,这里涉及到文件句柄问题,读者知道即可,解决方案就是,不开集群~ 哈哈,只开一个服务,
这时候读写速度堪比火箭~ 堆积几百万的数据,一分钟不到全部读写完~

这个服务在线上跑了巨久的时间,没有出现过问题,当然运维一定要关注你们的服务器磁盘,不要出现磁盘快被写满出现的各种异常问题~ 大数据和硬件存储息息相关~

这里不去探讨logback对文件写入到底作了何种优化,如果读者碰到同样的问题,可以参考本文章,采用logback方式,去解决这个问题~

谢谢阅读

发布了38 篇原创文章 · 获赞 17 · 访问量 9020

猜你喜欢

转载自blog.csdn.net/cainiao1412/article/details/99174718