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

8 篇文章 0 订阅
2 篇文章 0 订阅

笔者参与过某线上项目,该项目的数据存储量巨大,大概每秒都有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方式,去解决这个问题~

谢谢阅读

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是使用logback将日志数据写入mysql数据库的示例代码: 1. 添加依赖 在pom.xml文件中添加以下依赖: ``` <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>1.2.3</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.25</version> </dependency> ``` 2. 配置logback.xml文件 在src/main/resources目录下创建logback.xml文件,并添加以下配置: ``` <configuration> <appender name="DB" class="ch.qos.logback.classic.db.DBAppender"> <connectionSource class="ch.qos.logback.core.db.DriverManagerConnectionSource"> <driverClass>com.mysql.cj.jdbc.Driver</driverClass> <url>jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai</url> <user>root</user> <password>123456</password> </connectionSource> <sqlDialect class="ch.qos.logback.core.db.dialect.MySQLDialect"/> <insertHeaders>true</insertHeaders> <bufferSize>1</bufferSize> <tableName>log</tableName> <columnMapping> <column name="timestamp" isTimestamp="true"/> <column name="level" pattern="%level"/> <column name="logger" pattern="%logger"/> <column name="message" pattern="%message"/> </columnMapping> </appender> <root level="info"> <appender-ref ref="DB"/> </root> </configuration> ``` 其中,url、user和password需要根据实际情况修改。 3. 编写测试代码 在Spring Boot应用程序中,可以使用LoggerFactory获取Logger实例,并使用Logger实例记录日志。例如: ``` @RestController public class TestController { private static final Logger logger = LoggerFactory.getLogger(TestController.class); @GetMapping("/test") public String test() { logger.info("This is a test log message."); return "success"; } } ``` 4. 运行测试 启动Spring Boot应用程序,并访问http://localhost:8080/test,可以在mysql数据库中查看到日志数据

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值