友情链接
大数据场景下会产生海量文件,其中,小文件会对系统造成一系列影响。在实际业务中,小文件现象出现频率并不低,客户现场开发环境和或生产环境多或少都会遇到小文件问题,这些问题或来自上游系统,亦可能是因为表的分区分桶不合理,也可能是来自于不规范的sql等等。
当小文件过多时,将会导致内存占用高、集群不稳定,增加计算资源的开支等一系列问题。
因此小文件治理是必要的也是迫切的。因此,本篇文章将从源头为您介绍什么是小文件合并,为什么会出现小文件增多的情况,不同阶段下治理小文件的最佳手段,以及不同的表格式中的合并机制是什么,如何使用。感兴趣的小伙伴一起看下去吧~
为了更清楚的理解我们为什么要治理小文件,Compact机制是什么,我们需要溯源,了解小文件出现的原因是什么,有什么弊端。
HDFS(Hadoop Distributed File System)是Hadoop项目的核心子项目之一,旨在通过计算机集群在分布式环境中有效地存储和处理大批量文件,有效地解决大规模、海量数据的存储以及读写性能的问题,并为海量数据提供了不怕故障的存储,为超大数据集的应用处理带来了很多便利。
HDFS常常用于在Hadoop生态体系内提供分布式文件读写服务,目前大数据平台很多都在使用HDFS应对海量数据的存储。
HDFS有以下的设计目标:
虽然HDFS有效解决了用户大文件存储等问题,但其本身设计存在一些缺陷。我们先来看一下HDFS的简易架构图:
从整个系统架构上看,NameNode扮演着十分重要的角色,其一大功能是负责进行元数据的存储与管理。其中元数据信息包括文件名、文件所在路径、文件所有者、副本数等信息。此外,还有多个 DataNode 节点,这些节点就是文件存储位置。客户端收到服务器允许上传文件的响应之后,会将该文件分为一个个块(block),每个块默认大小为 128MB,将每个块依次发送到 DataNode 中,由 NameNode 记录块的存储位置等信息存储在元数据中。为了保证数据有足够多的副本,这时服务器会进行一个异步的操作,将这个块再进行复制操作,存储到其他 DataNode 中。
因HDFS的基于block块存储、3副本机制等设计特点,当面对低延时、数据量小且文件多的场景便会引发小文件问题。
小文件是指文件size远小于HDFS上block块大小的文件。小文件数量过多,会给hadoop的扩展性和性能带来严重问题。
任何block块、文件或者目录在内存中均以对象的形式存储,如果小文件的数据量非常多,则NameNode需要更多存储元数据的空间,将逐渐出现内存受限的问题,并且很多存储小文件时被隐藏的问题被暴露出来,比如启动时间变长(NameNode的启动过程通常分为几个阶段,包括fsimage本地数据加载、读取JournalNode上比fsimage新的editlog、在本地进行editlog replay、DataNode的BlockReport)。这样NameNode内存容量严重制约了集群的扩展。
并且,有关元数据的管理处理等操作基本都是基于NameNode来进行,那么当内存被大量占用时,对于元数据的增删改查的操作性能会出现下降的趋势,相对于更复杂的操作处理,比如RPC (Remote Procedure Call) 请求的性能下降趋势将会更加明显。
HDFS最初是为流式访问大文件开发的,访问大量小文件,block块在不同的节点上,则需要不断的从一个DataNode跳到另一个DataNode,严重影响性能。
即使NameNode能够存储大量的小文件信息,对于hive,spark计算时,小文件同时也意味着需要更多的task和资源,处理大量小文件的速度远远大于处理同等大小的大文件的速度。这是因为每一个小文件要占用一个slot,而task启动将耗费大量时间甚至大部分时间都耗费在启动task和释放task上,同样也会导致节点异常。
因此,总的来说,导致出现小文件的原因主要有以下几类:
① 用户在进行小批量、频繁的数据写入和更新操作的时候,会产生大量的小文件;
② torc表compact多次合并失败后进入黑名单,导致小文件不再继续合并;
③ 采用不规范的sql语句,导致单次事务产生的都是小文件;
④ 往动态分区插入数据可能会产生大量的小文件,从而导致Map数量剧增。这是因为假设动态分区有n个分区,数据插入动态分区阶段产生了m个Map任务,则会产生n*m个文件,从而带来很多小文件。对于多级动态分区,n值往往会变得很大;对于大的数据量,m值往往会变得很大;
⑤ 表设计不合理:
⑥ HDFS的设计建立在更多的响应"一次写入、多次读写"任务的基础上。这意味着一个数据集一旦由数据源生成,就会被复制分发到不同的存储节点中,然后响应各种各样的数据分析任务请求。HDFS是为了处理大型数据集分析任务的,主要是为达到高的数据吞吐量而设计的,这就可能要求以高延迟作为代价。比如我们使用 Spark Streaming 从外部数据源接收数据,然后经过 ETL 处理之后存储到 HDFS 中。这种情况下在每个 Job 中都会产生大量的小文件。
⑦ 数据源自身存在问题:比如历史数据流程导致小文件问题,这些数据一般是从别的数据库迁移过来,后续没有进行治理;也可能数据源本身就是大量的小文件或落盘参数不合理。例如需要接入的数据文件即是大量文件,如照片,文档等非结构化数据的接入(原始数据就是大量小文件,所以存储到HDFS上必定也是小文件);
综上所述,出现小文件问题的可能性有很多,从上游到下游的各个步骤都有可能产生小文件,但无论是什么原因,都有可能会导致下述影响:
因此,小文件是特别常见的现象,解决小文件问题迫在眉睫!!!一般来说,一个任务是由几个步骤组成的,而小文件的产生也来自任务的各个流程和步骤:
上游 => 本地文件系统 => HDFS => Map => Reduce => FileSink
所以解决小文件问题就是需要从最前面的步骤中入手。而且解决小文件的隐患,肯定也是越早越好,就像过滤下推,能尽早做的过滤条件一定是尽早过滤。
因此,能在本地文件系统解决的问题,没必要放到HDFS上解决。就像前面章节的内容所述,HDFS本身就不适合存储大量小文件,小文件过多会导致namenode元数据特别大, 占用太多内存,严重影响HDFS的性能。
当前治理小文件的现有手段主要有以下几种:
存储端合并主要是合并已经写入到存储的文件,当表中有小文件或者小文件过多时,可以使用存储端合并的方式来治理小文件。存储端合并的方式主要适用于以下原因导致的小文件问题:
需要注意的是,不同的表格式的合并方式不同。
针对Torc及Holodesk表星环均提供了Compact机制,用户可以根据业务特点来设置一些参数自定义小文件的自动合并策略以平衡小文件数量和合并开销,比如设置定期合并策略或者设置触发阈值等。
除此之外,针对Holodesk表用户可以使用星环新推出的Compact Service(小文件合并专用服务)进行小文件合并任务。常见的小文件合并功能是通过计算引擎服务来执行 Compact 任务的,可能会占用部分计算资源。新推出的Compact Service则在组件级别做了隔离,开启后不会影响到Quark的查询计算性能,合并效果更好。
除了上述的自动合并机制之外,用户也可以通过在星环运维管理平台DBA Service查看当前的小文件数量,管理Compact任务,如果检查发现小文件数量过多,可以通过同步或异步的方式手动执行小文件合并任务,减少小文件数量。
由于篇幅原因,更多内容详见:
对于holodesk及torc表用户可以采用自动或手动的形式进行合并,或者使用最新的Compact Service,但是针对text及orc非事务表来说,一直以来没有一个很完美的合并方法,通常是在小文件很多出现问题了再去解决,比如重建表,重新导入数据。
但是这样的方法是没有办法真正的预防与根治小文件问题。
Galactus应运而生!!
Galactus可以自动检测到小文件自动合并掉,用户无需担心因为处理不及时或有疏漏影响到业务系统,更加贴合生产上的需求。
小文件合并功能在数据管理和处理中具有重要性,尤其在分布式存储和计算系统中更为显著。除了可以预防问题的产生之外,使用Galactus还具有以下好处:
总体而言,小文件合并功能对于优化存储资源、提高数据访问效率、降低网络传输开销以及优化计算性能都起着重要作用,特别是在大规模数据处理和分布式环境中。因此,Galactus至关重要!!
由于篇幅原因,更多客户应用案例及使用Demo内容详见: Text/ORC非事务表合并最佳方式
除了针对上述表实现的合并机制外,星环分布式分析型数据库ArgoDB在6.0及后续版本中引入了归档分区的功能,用户可以跨分区进行合并。归档分区的实现手段是用范围分区去表示单值分区,适用于分区字段为离散类型的单值分区,如日期 (暂不支持时间戳类型)、整数的场景。
通过分区归档合并功能,可以将大量小文件(归档前的单值分区)合并成较大的文件(归档后的范围分区),从而减少存储开销、元数据管理的开销以及处理时的任务调度开销。
由于篇幅原因,更多使用方法可参考: 归档分区功能使用方法
计算端合并分为两个阶段:map端合并和reduce端合并,适用于以下原因导致的小文件问题:
正如前面所说,小文件的产生来自任务的各个流程和步骤,越早解决对业务的影响越小。如果在前期无法解决的情况下,可以考虑在以下两个阶段进行合并:
maptask阶段合并可以使用automerge功能,在map端控制map task的数目,它可以根据每个partition (数据块) 所在的位置及大小将多个partitions交给一个task去完成。在星环TDH9.3及后续版本中,还引入了automergeV2功能,V2版本在性能方面有很大提升。
使用的时候需要注意:
① ngmr.partition.mergesize.mb=-1, 则表示不关心数据块的大小,会将(1 + mergesize)个数据块合并到一个task去做;
② ngmr.partition.mergesize.mb!=-1,则表示额外merge的数据块的大小超过mergesize.mb时,停止merge;
③ ngmr.partition.mergesize和ngmr.partition.mergesize.mb只有一个参数可以生效,-1则表示不生效,如果两个参数同时设置,则生效的是参数ngmr.partition.mergesize.mb;
④ mergesize和mergesize.mb设置过大会导致gc,建议mergesize设置范围为:10-1000,mergesize.mb设置范围:10-200;
需要注意的是,该功能当前可能存在一些问题,比如
① 1.merge之前不检查自身大小,会导致将两个size都很大的数据块合并在一起,导致数据倾斜;
② automerge的参数如果设置的太大,可能会导致executor gc;
③ ngmr.partition.mergesize.mb和ngmr.partition.mergesize不能同时生效;
在TDH9.3的后续版本中,产品引入了改进后的automergeV2功能,V2版本在性能方面有很大提升,同时该功能也解决了上述有关automerge可能存在的一些问题,比如:
① 合并到一组的partitions的size之和总会大于 ngmr.partition.mergesize.mb 的设置值,即size之和大于阈值的时候才会认为merge满了,导致的问题是会合并两个大的partition,造成单task执行时间长;
② 大文件很多(即要使用ngmr.partition.mergesize.mb参数),小文件也很多(即要使用ngmr.partition.mergesize参数)时,只用前者那个参数,那么问题就是会把数量巨多的小文件合并到1个task,虽然每个文件里数据量很少,但是不停的create reader是1个很耗时的过程;如果使用后者那个参数,那会把大文件合并到1个task。都会造成单task执行时间长;
因此,具体如何使用automergeV2功能呢?操作方法如下:
1) 使用automergeV2遇到不支持的情况会自动使用老版本,其他参数功能不变
2) 如果需要使用老版本 automerge,参数如下:
当小文件过多引起后续reduce任务数量暴增到一定值的情况下,引擎出于自我保护会中断任务并返回一些报错提醒,因此可以设置合理的reduce个数(mapred.reduce.tasks),以保证单reduce处理合适的数据量。
设置mapred.reduce.tasks数可以增加初始化性能,建议 500w数据量设置一个tasks。同时也可以按照文件的大小来设置tasks数据,如果小于500M,则设置为5,如果大于500M,则是文件大小/300M设置,根据hdfs上文件总大小合理控制。也可以将reduce数设定为集群vcore的一半,既保证vcore被充分利用,又不影响其他程序的工作。但是最终的mapred.reduce.tasks尽量小于1000。
但归根结底,治理小文件的最优解一定是从源头(集群规划设计和搭建)开始就进行规范管理。比如在表结构和SQL语句方面进行规范,特别是分桶个数,分区类型的选择,范围分区跨度的选择。如果从初期就开始重视,确保不会出现小文件过多以及合并完成后还是小文件的问题,可以最大程度的减少后续因该问题引发的集群异常和宕机的可能,进一步达到治理小文件的目的。
SQL端规范适用于以下原因导致的小文件治理:
分桶表表结构创建之前需要对表的数据分布情况进行大致的分析,一般遵循的原则为,选择离散度高的字段进行分桶,避免选择decimal类型的字段做为分桶键,一般选择表的主键等字段。包括账号,客户号,证件号码等。分桶个数选择非31的质数,分桶字段选择时,注意尽量使记录分布均匀,以避免数据倾斜,设计表结构时首先要预估该表的数据量和数据文件的大小,按照每个桶文件100-200M的大小来设置分桶个数。
另外,在考虑分桶个数的时候,同时要考虑是否已经分过区。对于已经分过区的表,要按照单分区的大小进行桶数的估计,而不是依照原始表。
建议创建范围分区表,不推荐使用二级分区,分区字段选择上一般选择时间,区域等字段,并根据数据的每天和每月增量的大小来界定每个分区的范围。如果一个分区数据量太大,可以考虑创建分区分桶表,并合理的设置分桶键和分桶个数。
另外,在考虑分桶个数的时候,要考虑单分区的大小,要按照单分区的大小进行桶数的估计,而不是依照原始表。
对于由于表结构原因导致小文件过多的表,建议是重建表结构,一般具体流程为:备份表和数据-->重建表结构-->回填表数据-->验证表数据和文件-->删除备份表。
注意:对于分区表、分桶表、分区分桶表,回填数据时,可以sort by备份表的分区键、分桶键来提升表的查询效率。