一次大数据文件处理日记

本文涉及的产品
云原生大数据计算服务MaxCompute,500CU*H 100GB 3个月
简介: 一次大数据文件处理日记

前言:


最近在做业务功能的时候,拿到一个非常"简单"的需求,把一个 30万行的数据文件按照特定的格式进行入库,文件格式和字段的内容都有对应的规定。这种需求其实还算比较常见,通常这一类需求不管系统配置多么强悍,都不可能无脑的读取插入。趁着这个需求搜集了一下几种常见的做法。下面就来介绍一下解决这种大数据文件的常用套路。


文章目的:


  1. 在JAVA中如何安全的将一份超大文件进行安全入库处理方式。
  2. 大文件读写可能产生的性能问题和瓶颈分析
  3. 关于分析大文件读写的常见套路
  1. 使用单线程还是多线程
  2. 多线程的相关问题讨论


文件内容分析


由于实际的情况复杂多变,在做具体的编码之前,需要先梳理有可能存在的情况,下面简单列举系统有可能的存在的问题,和一些常见的注意事项:

  • 系统硬件水平,服务器是否会因为读写大量的数据文件占用大量资源
  • 内存问题:加载大数据最容易出的问题那就是爆内存,建议至少使用缓冲流进行读写
  • 硬盘问题:读写的限制另一种体现就是硬盘的好坏,固态优于机械的读写.
  • 文件的读写方式,JAVA的IO比较复杂,这里简化为三种也就是常见的BIO、NIO、和AIO(具体代表含义请自行百度)。
  • 异步IO虽然看起来很美,但是需要考虑顺序入库的问题。
  • 多线程异步读写比较考验机器性能,请谨慎使用。
  • 顺序读写永远是硬盘最快捷的一种方式
  • 完成一次完整的操作时间估量,既然是大文件,就必然需要考虑整个操作的执行时间,一份几十万的数据跑一轮下来不管如何优化肯定需要不少的时间,所以操作的时间消耗需要考虑在可接受的范围
  • 大数据文件读写的时间选择
  • 通常比较重和累的活都放大半夜去干
  • 估量整个任务的执行时间消耗

这些分析只是一些最基本的要求,不同的业务场景会有更多的细节考量,文章不可能面面俱到,这些分析更多的是帮助个人提高警惕性,只有考虑到所有可能想到的细节,这样的大文件读写才可能是安全可靠的,同时可以保证突发情况可以及时的反应。

最后,这类开销比较大的操作,对于日志打印和记录的计算需要额外小心,最好在一次较大操作中记录操作成功失败记录数,同时在整个记录完成之后通过日志持久化整个操作的结果。


大文件读写的常见套路



其实这些套路网上多看看资料基本都可以有自己的一套方案,下面给出的建议可能不是最好的方式,有些可能在实际业务场景下走不通。(完全有可能)但是借着这些套路希望可以给读者一些启发,下面我们直接进入主题。


分批入库


分批入库是最容易想到的方式,也是最保险最稳妥的方式,这里包含了一个隐式的条件,就是数据都是增量不改动数据,大致意思就是不会改动的固定数据库数据。

现在我们来看下分批入库是如何处理的,分批的意思就是说每N条进行一次操作,防止数据库突然收到一个巨量的Insert请求导致锁表并且影响业务(弱一点的服务器直接满载),下面根据一段案例代码来说明做法:


个人公司的电脑是一块SATA的固态硬盘,在开启批量操作之后,经常100%读写占用系统假死,所以如果要进行试验,建议先设置一个很小的值慢慢加量,否则你的电脑可能会卡的动不了。

  • 首先需要编写一个批量插入的sql语句,网上对应案例的语句如下(如果是mybatis,需要使用<foreach>标签标记需要循环的对象内容):


INSERT INTO table ( "clo1", "col2", "col3", "col4", "col5" )
VALUES
( 1, 10, NULL, '2019-12-19 13:38:35', '新年活动16张卡券'),
( 2, 11, NULL, '2019-12-19 15:05:13', '圣诞活动11张卡券'),
( 3, 12, NULL, '2019-12-19 15:05:13', '圣诞活动12张卡券'),
( 4, 13, NULL, '2019-12-19 15:05:13', '圣诞活动13张卡券');
复制代码


  • 下面是分批操作的JAVA代码,大致逻辑是打开一个文件,然后将一行数据转为一个对象,同时塞入到一个集合当中,当集合的内容超过限制的时候,进行一次入库的操作。


private void insert2DbByBatchList(Config config, String line) throws IOException {
        List<VisaNewBinVo> insertList = new ArrayList<>(1000);
        Map configValue = readConfigValue();
        while (StringUtils.isNotEmpty(line)) {
            Timestamp timestamp = new Timestamp(System.currentTimeMillis());
            VisaNewBin visaNewBin = new VisaNewBin();
            configValue.forEach((key, value) -> {
                Map<String, Object> visaBinField = (Map<String, Object>) value;
                Integer endInex = (Integer) visaBinField.get("endInex");
                Integer startIndex = (Integer) visaBinField.get("startIndex");
                if (startIndex < line.length() && endInex < line.length()) {
                    String substring = line.substring(startIndex, endInex);
                    FieldReflectionUtil.setFieldValueByFieldName(visaNewBin, key.toString(), substring);
                }
            });
            VisaNewBinVo visaNewBinVo = new VisaNewBinVo();
            BeanUtils.copyProperties(visaNewBin, visaNewBinVo);
            visaNewBinVo.setBinId(UUID.randomUUID().toString());
            visaNewBinVo.setBatchNo(getVisaNewCardBinDecAfterFileName(config));
            visaNewBinVo.setCreateTime(timestamp);
            insertList.add(visaNewBinVo);
            // 限制部分
            if (rechLimitValue(insertList)) {
                int count = visaNewBinMapper.batchInsertNewBins(insertList);
                logger.info("当前批次数据为:{} 条,成功入库: {} 条数据", insertList.size(), count);
                insertList.clear();
            }
        }
    }
    private boolean rechLimitValue(List insertList) {
        return insertList.size() % 500 == 0;
    }
复制代码


小贴士:很多人可能会认为可以用Thread.sleep(1000)类似的线程休眠的方式让计算机“冷静”一下,给数据库一些缓冲时间,但是其实从大文件读写的角度来看,没有太大的意义,因为我们的文件读写要么需要开一条“河流”,要么就像新的方式直接开一条“矿道”(底层IO)。我们一旦打开流或者开通矿道就是在占用系统资源。用这种休眠的方式无非就是拉长了整个工作的时间,其实并没有太大的实际意义。

当然这种形式并不是完全没有任何作用,有些情况下比如之前个人曾经做过关于一个百度的分析接口存在QPS个位数限制的情况下,这种时候最简单的方法就是使用线程休眠来限制调用。

当然这种形式在编码里面比价丑陋,可以使用JDK的工具类TimeUtil来更加优雅的细粒度控制线程休眠时间控制。

这里有个八股文的面试题Thread.sleep(0)的含义。


分批入库存在的问题


分批入库虽然是最无脑的一种方式,但是这里其实是存在限制的,一般会存在下面这些问题:


  • 数据库对于preSql的占位符限制:比如postgreSql 的限制为Short类型的最大值,即32747,超过这个值就会抛出如下的异常:

Tried to send an out-of-range integer as a 2-byte value

github上面有人提过这个issue,里面还有一些老外的吐槽,挺有意思的,文章连接:

github.com/pgjdbc/pgjd…如何解决"尝试将超范围整数发送为 2 个按次值"的错误#1311

stackoverflow.com/questions/2…PostgreSQL ERROR: INSERT has more target columns than expressions, when it doesn't

如果想要绕开这个问题,可以自己手写一个实现类进行替换。还有一种办法就是减少占位符,增加批次然后减少每次批次的插入数据量。

  • 硬件水平的限制:这里主要说的是硬盘上的限制,一块差点的硬盘即使是分批操作也会卡死,需要注意分批之后不是高枕无忧了

硬件问题不能完全作为无法解决问题的借口。

  • 程序中断的影响:分批的方式比较常见的一个问题是处理入库过程中 程序异常断电系统故障(蓝屏)

一种推荐的解决方式是数据库设置唯一校验字段,每次入库之前检查是否存在标记,可以使用redis进行辅助。(布隆过滤器)


多线程读写


多线程的处理方式也比较容易理解,既然一个人读写吃力,那就把文件“劈”成很多份,比如文件的第1条到1万条为线程1,第10001条到20000条为线程2, 依次类推,这种方式需要提前计算数据行的总量,然后开启线程将数据行分配给多个线程,由于个人处理的时候,被禁止使用多线程的处理方式,这里的代码为一些案例作用。

(建议PC端查看)

java读取大文件,采用多线程处理对提高效率可有帮助?

使用多线程会加快文件读取速度吗?


总结:


通过这次的小需求整理了一下大数据问题的处理经验,也算是对个人的一点提升。比较关键的是掌握多线程写入文件,需要考虑的内容还不少。不过网上的资料并不是特别多,还需要花更多的时间去研究。

相关实践学习
基于MaxCompute的热门话题分析
Apsara Clouder大数据专项技能认证配套课程:基于MaxCompute的热门话题分析
相关文章
|
关系型数据库 MySQL 数据库
|
5月前
|
SQL 关系型数据库 MySQL
MySQL 5.6/5.7 DDL 失败残留文件清理指南
通过本文的指南,您可以更安全地处理 MySQL 5.6 和 5.7 版本中 DDL 失败后的残留文件,有效避免数据丢失和数据库不一致的问题。
|
6月前
|
SQL 自然语言处理 关系型数据库
通义灵码2.5来袭!MCP 功能直接让开发效率提升300%(附实战案例)
通义灵码2.5是阿里云推出的AI编码助手,以智能协作为核心,深度融合开发全流程。其三大升级点包括:编程智能体实现任务自主规划、MCP工具生态支持自然语言生成SQL、记忆进化系统个性化适配开发者习惯。通过自然语言即可完成数据库操作、代码生成与优化,大幅提升开发效率。此外,还具备工程级变更管理、多文件协同编辑及版本控制功能,适用于多种IDE环境,为企业提供安全高效的开发解决方案。
|
监控 应用服务中间件 Linux
轻松解决日志文件积压问题:掌握logrotate的技巧
轻松解决日志文件积压问题:掌握logrotate的技巧
1188 1
|
关系型数据库 MySQL
MySQL主从异常Coordinator stopped because there were error(s) in the worker(s). The most recent failur
MySQL主从异常Coordinator stopped because there were error(s) in the worker(s). The most recent failur
5522 0
|
存储 数据库 开发工具
开发者如何使用云数据库 ClickHouse
【10月更文挑战第21天】开发者如何使用云数据库 ClickHouse
660 1
|
存储 SQL 监控
【Clickhouse 探秘】你真正知道 Clickhouse 吗?
ClickHouse 是一个开源的列式数据库管理系统,专为在线分析处理(OLAP)设计。它由 Yandex 开发并于 2016 年开源。ClickHouse 以其高性能、实时数据处理能力和易用性著称,广泛应用于大数据分析、日志处理和用户行为分析等领域。其主要特点包括列式存储、向量化执行、分布式架构、丰富的数据类型和 SQL 支持。
1365 4
|
存储 SQL Docker
ClickHouse入门指南:快速搭建与使用
【10月更文挑战第26天】在大数据时代,如何高效地处理海量数据成为了许多企业和开发者的关注点。ClickHouse 是一个开源的列式数据库管理系统(Column-Oriented DBMS),以其出色的查询性能和高并发能力,在数据分析领域迅速崛起。本文将从一个初学者的角度出发,详细介绍如何快速上手 ClickHouse,涵盖从环境搭建到基础操作的全过程。
1570 3
|
安全 Java API
Java日志详解
Java日志详解
748 0
|
存储 固态存储 关系型数据库
PostgreSQL核心操作之数据备份恢复
PostgreSQL核心操作之数据备份恢复
1215 0