前几天帮一个部门排查Spark作业频繁OOM的问题最后发现根子不在代码而在HDFS上堆了上千个未压缩的Parquet小文件。这个事让我觉得有必要把Hadoop压缩/解压缩这件事彻底写透——它绝不是省点磁盘的小事而是贯穿HDFS读写、MapReduce Shuffle、Spark传输每个环节的IO关键动作。作为泰涨知识系列里Hadoop IO部分最容易被低估的一环压缩和解压缩直接决定了集群能不能扛住业务增长。这篇我会把压缩解决的问题、主流格式怎么选、底层API怎么调、MapReduce/Hive/Spark里怎么配以及那些真正让我头疼过的坑都摊开讲。适合正在用Hadoop生态做数仓、做离线ETL、或者刚接手一个跑不动的集群、想搞清楚压缩到底该怎么用的人。看完你至少能回答三个问题我的数据该不该压用什么格式压压完之后Map任务为什么变少了1. Hadoop压缩到底解决什么问题存储、网络与调度的三重压力1.1 压缩介入的位置从写入HDFS那一刻就开始在Hadoop体系里数据写入HDFS默认要在不同节点落三份副本。假设一个业务表原始日志是100GB三副本就是300GB物理占用。如果写入前先做一次压缩压缩率按60%算这在日志文本里很常见那么物理占用直接降到120GB左右——这一下省下来的不是一点点而是整个机架的硬盘采购成本。我见过有个集群磁盘使用率常年卡在89%加了压缩配置后一个月内降到70%以内相当直观。压缩不只是入库前压一次这么简单它介入的位置贯穿整条IO链路客户端写HDFS时数据从本地上传到DataNode网络传输的是压缩后的字节写完三副本后再解压或直接存储压缩态。Map任务读输入时从DataNode拉取数据如果输入文件是压缩的先解压成原始字节再交给RecordReader。Map输出到Shuffle时Map端溢写spill和合并merge的中间结果可选择压缩显著减少写本地磁盘的量。Reduce拉取数据时从各个Map端拷贝中间结果网络传输压缩数据到Reduce端再解压。最终输出时Reduce结果写回HDFS可配置压缩。所以Hadoop IO操作里的压缩本质上是一条数据流水线上的多个压缩开关每个开关控制的是不同阶段的时空转换。1.2 不只是省磁盘用CPU换IO的账要算清楚压缩的本质是用CPU时间换存储空间和网络带宽。很多人以为凡是能省空间就一定要压其实不然。压缩和解压都是计算密集操作如果一个集群的瓶颈本来就在CPU上强行压缩反而会让作业更慢。反过来如果集群IO带宽已经打满、磁盘排队严重压缩几乎总是有益的因为省下来的IO时间远超压/解压消耗的CPU时间。我做了一个很粗略的经验法则对于可压缩性强的文本数据日志、JSON、CSV压缩带来的收益在90%以上场景里是正向的对于已经是高密度二进制的数据比如已经是压缩过的图片字节流、加密数据、随机数据压了等于白压还会白烧CPU。判断数据能不能压最简单的方式是拿一个文件跑一下gzip -t或者压完看体积变化率低于10%就直接放弃压缩。1.3 先理清一个概念序列化和压缩不是一回事在Hadoop里序列化比如Writable、Avro的Object Model负责把内存对象变成字节流解决的是怎么把结构化数据塞进流里的问题压缩负责把字节流再变小解决的是字节流太大怎么传怎么存的问题。两者是两个独立的层次可以单独使用也可以组合使用。很多人把Hadoop的SequenceFile压缩机制和文件格式压缩混在一起其实SequenceFile的压缩有三种模式无压缩、记录压缩record compressed和块压缩block compressed。块压缩模式会在一个数据块内先做序列化然后对整块字节压缩这种设计特别适合小记录多的场景。搞清楚这层关系后面理解文件格式内外两层压缩才不会晕。2. 主流压缩格式怎么选压缩比、速度与可切分性的三角权衡2.1 五种格式的实测对比Hadoop生态里能遇到的压缩格式基本就是下面这几种Gzip、BZip2、LZO、Snappy、Zstandardzstd。我把它们放在一张表里对照看。格式压缩比压缩速度解压速度是否原生可切分生态支持情况Gzip.gz较高中中否几乎所有版本默认支持BZip2.bz2最高慢慢是按块magic分隔Hadoop内置支持LZO.lzo中等快快需额外建索引需安装gplcompression或原生库Snappy.snappy中等偏低极快极快否Hadoop支持常配合Parquet/ORCZstandard.zst高可调级别快快否较新版本Hadoop支持需关注native库很多人看到BZip2压缩比最高就直接选它结果作业跑得奇慢无比——BZip2的压缩速度差不多是Gzip的四分之一到三分之一解压也慢。BZip2在Hadoop里真正适合的场景是冷数据压缩或需要高压缩比又不追求速度的归档表。Snappy相反压缩比一般但速度是所有格式里最顶级的适合中间结果和实时链路。2.2 可切分性不是加分项而是正确性问题可切分性Splittability是Hadoop压缩选型里最容易被忽略、但影响最大的一个属性。要理解它得先明白HDFS块和MapReduce分片split的关系。一个未压缩的大文件存储在HDFS上时会被切成128MB旧版本是64MB的块分布在多个DataNode上。MapReduce在读取文件时可以把不同的块分给不同的Map任务并行处理。对于未压缩文件块边界就是安全的数据边界——每个Map只需要从块的起始偏移量开始读。但Gzip不是这个逻辑。Gzip是一种流式压缩格式整个文件是一个连续的压缩流后面的数据解压必须依赖前面的数据状态。虽然文件在HDFS上被拆分成了多个块但每个块里的压缩流并不独立Map任务如果只拿第二个块它压根不知道解压上下文是什么。所以FileInputFormat对gzip这种格式直接调用isSplitable返回false把整个文件当成一个split交给一个Map处理。你可以理解为未压缩的文件像一盒独立包装的小饼干谁拿一包都能直接吃而Gzip像一个巨大的切糕必须从头开始啃第一口没啃到的人什么都吃不到。如果数据量从2GB涨到20GB还是只有一个Map在跑其他几百个核都在看热闹这就是典型的压缩导致的并行度崩塌。2.3 结合场景给出我的选型结论我在实际项目里的默认选择遵循一套简单的规则离线全量数仓基座表BZip2或Zstd的高压缩级别这类表读取频率低、存储成本敏感慢一点无所谓。实时链路快照、中间交换数据Snappy或LZO要快压缩比差不多就行。交互式查询Hive/Spark SQL查明细Parquet/ORC格式 Snappy或Zstd利用列式存储内部的压缩而不是外层套压缩。冷数据归档Zstd压缩级别调高在压缩比和解压速度之间取得比BZip2更好的平衡。需要MapReduce高并行读取的纯文本压缩文件LZO建索引或者干脆不要在文件层压缩改用SequenceFile/Parquet容器格式。这套规则并不是拍脑袋定的背后就是压缩比、速度、可切分性三角权衡。你要做的第一件事永远是确认自己的数据要过什么样的IO链路再选格式。3. 核心机制CompressionCodec与压缩流的完整调用链3.1 一套Codec接口N种实现Hadoop把压缩和解压缩抽象成了一个接口org.apache.hadoop.io.compress.CompressionCodec。每种格式就是一个Codec实现类比如GzipCodec、BZip2Codec、SnappyCodec、LzoCodec、ZStandardCodec。CompressionCodec接口里有两组关键方法createOutputStream(OutputStream out)给底层的输出流包一层压缩输出流write进去的数据会被压缩后写入底层流。createInputStream(InputStream in)给输入流包一层解压输入流read出来的数据是解压后的原始字节。getDefaultExtension()返回该Codec对应的文件后缀比如GzipCodec返回.gzSnappyCodec返回.snappy。初次接触的人容易误以为Hadoop里压缩需要自己到处手写Codec其实MapReduce框架在读写文件时已经内置了Codec的解析和调用。真正需要你直接操作Codec的场景通常是自己写InputFormat、自定义文件写入工具、或者在客户端代码里要单独压缩一批文件。3.2 文件后缀到Codec的自动识别机制在Hadoop里做压缩有一个很nice的特性只要输入文件的后缀符合约定MapReduce和HDFS层面会自动匹配对应的Codec进行解压完全不需要在上传时记录这个文件是压缩的之类的元数据。这个匹配逻辑由CompressionCodecFactory实现。它会遍历配置项io.compression.codecs里注册的所有Codec然后拿文件扩展名去匹配。比如文件后缀自动匹配的Codec.gzGzipCodec.bz2BZip2Codec.lzoLzoCodec.snappySnappyCodec.zstZStandardCodec.deflateDeflateCodec如果你需要注册一种自定义Codec只需在core-site.xml里追加格式property nameio.compression.codecs/name value org.apache.hadoop.io.compress.GzipCodec, org.apache.hadoop.io.compress.BZip2Codec, com.example.MyCustomCodec /value /property注意io.compression.codecs是逗号分隔的类名列表顺序无所谓但别重复注册。自定义Codec必须完整实现CompressionCodec接口并在构造函数里持有Configuration对象。3.3 手写一个压缩写、解压读的Helper虽然框架里大部分场景不用你亲自动手但理解完整调用链仍然很有价值。我给你一个非常精简的示例演示如何在客户端代码里把一个文件压缩后写入HDFS。import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.fs.FSDataOutputStream; import org.apache.hadoop.fs.FileSystem; import org.apache.hadoop.fs.Path; import org.apache.hadoop.io.compress.CompressionCodec; import org.apache.hadoop.io.compress.CompressionCodecFactory; import org.apache.hadoop.io.compress.CompressionOutputStream; public class HdfsCompressHelper { public static void compressAndWrite(String srcPath, String dstPath, String codecClass) throws Exception { Configuration conf new Configuration(); FileSystem fs FileSystem.get(conf); Path src new Path(srcPath); Path dst new Path(dstPath . getExtension(codecClass)); CompressionCodec codec new CompressionCodecFactory(conf).getCodecByClassName(codecClass); FSDataOutputStream fos fs.create(dst); CompressionOutputStream cos codec.createOutputStream(fos); byte[] buffer new byte[8192]; int len; try (java.io.InputStream is fs.open(src)) { while ((len is.read(buffer)) 0) { cos.write(buffer, 0, len); } } // 关键finish()会写入压缩格式的结尾标记 cos.finish(); cos.close(); fos.close(); } private static String getExtension(String codecClass) throws Exception { CompressionCodec codec (CompressionCodec) Class.forName(codecClass) .getDeclaredConstructor().newInstance(); return codec.getDefaultExtension(); } public static void main(String[] args) throws Exception { if (args.length ! 3) { System.out.println(Usage: HdfsCompressHelper srcPath dstPath codecClass); return; } compressAndWrite(args[0], args[1], args[2]); } }对应的解压读取方向代码逻辑对称codec.createInputStream(fs.open(path))然后循环read即可。这里我特别想提醒两点finish()和close()的顺序不能乱。finish()专门负责把压缩流内部缓冲的数据flush到底层流并写入格式要求的结束标记close()会调用finish()并关闭底层流。如果你只调close()结果通常没问题但如果你先close()再调用finish()会直接抛异常。反过来flush()只是把压缩缓冲推送到底层流不等于写出一个完整的压缩块极端情况下会得到不完整的压缩文件——这也是后面CRC排障篇的伏笔。CompressionCodecFactory内部会缓存一部分Codec实例但CodecPool管理的是压缩/解压器Compressor/Decompressor对象池。原因很简单Native压缩器比如Snappy的native实现的创建和销毁开销不小池化可以复用底层资源。在高并发客户端里不要自己频繁new压缩器能用CodecPool就用。3.4 SequenceFile里的压压缩进一层SequenceFile是Hadoop的原生二进制键值文件格式它本身也支持压缩。SequenceFile.Writer有个writeCompression参数可选CompressionType.NONE、RECORD或BLOCK。实际使用中BLOCK压缩远比RECORD压缩效果好因为块压缩将一批记录的字节整体压一次压缩冗余更大。如果你在写MapReduce输出时选择SequenceFile记得在作业配置里把mapreduce.output.fileoutputformat.compress.type设为BLOCK否则默认行为可能让你被小记录逐条压缩的可怜压缩比坑到。4. MapReduce/Hive/Spark中启用压缩三个阶段的配置实战4.1 输入、中间、输出三阶段分别在哪配MapReduce里的压缩分三个独立阶段配置项各不相同。很多人只配了一个输出压缩发现Shuffle还是慢就是漏了中间结果。第一阶段是输入文件本身已压缩。这个阶段基本不用任何配置MapReduce会根据文件后缀自动选用Codec解压。唯一要留意的是可切分性问题——如果是gzip这种不可切分的格式配置救不了你。第二阶段是Map输出中间结果压缩。这个阶段决定了Shuffle时Map端溢写文件的大小、落盘IO量、Reduce拉取数据的网络传输量。配置在客户端或mapred-site.xmlproperty namemapreduce.map.output.compress/name valuetrue/value /property property namemapreduce.map.output.compress.codec/name valueorg.apache.hadoop.io.compress.SnappyCodec/value /property因为中间结果只被Map和Reduce临时使用压缩比不是重点速度才是所以我几乎总是选Snappy。曾经有一个统计作业Map输出的中间结果大约40GB开启Snappy压缩后Shuffle带来的落盘量和网络传输量直接少了65%左右作业整体耗时缩短了三成。这只是个普通配置项收益却比很多人花大力气调的参数高得多。第三阶段是Reduce最终输出压缩。配置项是property namemapreduce.output.fileoutputformat.compress/name valuetrue/value /property property namemapreduce.output.fileoutputformat.compress.codec/name valueorg.apache.hadoop.io.compress.BZip2Codec/value /property property namemapreduce.output.fileoutputformat.compress.type/name valueBLOCK/value /property第三个compress.type只对SequenceFile输出有效。如果你的输出格式是SequenceFile建议用BLOCK如果是TextOutputFormat这个配置项不影响。BZip2通常是在最终结果要长期存储、而且后续会被不多次读取时才用如果下游要高频查询还是Snappy或Zstd更合适。4.2 在Java代码里动态设置不在XML里全局配置、只想在某个Job里开启时可以在代码中写Configuration conf new Configuration(); conf.setBoolean(mapreduce.map.output.compress, true); conf.set(mapreduce.map.output.compress.codec, org.apache.hadoop.io.compress.SnappyCodec); conf.setBoolean(mapreduce.output.fileoutputformat.compress, true); conf.set(mapreduce.output.fileoutputformat.compress.codec, org.apache.hadoop.io.compress.BZip2Codec); Job job Job.getInstance(conf);代码设置的优先级高于配置文件。所以如果你想只在某个特定作业里做实验又不想影响集群全局配置用这种方式最稳妥——不会因为调错一个参数把整个集群的作业节奏带崩。4.3 Hive和Spark的压缩配置对应关系Hive底层虽然早就不是纯MapReduce了但很多配置项沿用了同样的语义。在Hive里中间结果压缩hive.exec.compress.intermediatetrue编码器hive.intermediate.compression.codec。最终输出压缩hive.exec.compress.outputtrue编码器hive.exec.output.compression.codec。同时要注意hive.exec.orc.compression.strategy和hive.exec.orc.default.compress这两个控制的是ORC文件内部压缩不能和上面的外层压缩混为一谈。Spark方面重点有这几个spark.io.compression.codec控制Spark内部RDD中间数据落盘和Shuffle数据的压缩格式默认是lz4实际生产环境不建议改成lz4之外的除非你知道LZ4在Spark生态里的native实现最稳。spark.sql.parquet.compression.codecParquet文件里的压缩格式默认是snappy。spark.sql.orc.compression.codecORC文件里的压缩格式常用snappy或zlib。在Spark SQL里如果你建表用STORED AS PARQUET但外层又套了一个整文件的.gz压缩那绝对是个反模式因为Parquet内部已经按行组压缩了外层压缩只会让读取时先解压一遍外层再解析内部页白白浪费CPU。这个坑我后面会专门展开。4.4 怎么验证压缩真的生效了压缩配置写完之后怎么确认它真的生效我有三个土办法实测很好用。第一看运行Counter。在MapReduce的JobHistory里关注FILE_BYTES_WRITTEN、HDFS_BYTES_WRITTEN、HDFS_BYTES_READ这几个计数器。开启压缩前后各跑一次数值明显下降说明配置生效。比如原来HDFS_BYTES_WRITTEN是60GB开压缩后变成25GB就是压缩在起作用。第二看生成文件的后缀。如果输出目录里出现.gz、.snappy、.bz2结尾的文件说明最终输出压缩确实开了。这里有个小坑有时候文件后缀是part-r-00000.gz看起来没问题但里面其实没压这种情况多发生在你只改了Codec类名但没改后缀约定或者自定义输出格式没调用压缩流。可以用hadoop fs -cat直接看首字节gzip的首字节十六进制是1f 8bsnappy没有统一魔数但通常体积明显小于原始大小。第三在Web UI看启动日志。Hadoop的作业日志里Map任务启动时会打印类似compression codec org.apache.hadoop.io.compress.SnappyCodec的信息直接grep日志关键字就能确认。5. 切分问题的完整解决方案LZO索引与替代思路5.1 问题复现一个2GB的gzip文件如何让集群变单机我在一个生产环境踩过这个坑所以印象极深。某天的离线任务突然比平时慢了十倍上去一看输入是一天全量的用户行为日志上游给压成了一个大概2GB的gzip文件。HDFS块大小是128MB这个2GB文件被分成约16个块分布在集群各个DataNode上。但MapReduce对所有.gzip后缀文件统一返回splittablefalse结果就是整个2GB文件只启动了一个Map任务。一个Map任务要从头把2GB数据全部解压再处理跑了一个多小时。而集群里有几千个核全都闲着。我当时第一反应是文件太大换个格式但上游ETL已经成了生产依赖不能随便改。后来查资料才明白这事不能靠调配置解决得换一种能在块级别切分的压缩方案。5.2 LZO 索引文件让压缩流可以被逻辑切分LZO的压缩格式不是从头到尾一个连续流而是把文件切成了一个个可以独立解压的逻辑块。每个逻辑块有自己的压缩头理论上是可以从任意块起始位置开始解压的。但MapReduce框架并不知道每个逻辑块在文件里的偏移量所以光有LZO还不够还要额外生成一个索引文件。完整的做法分三步数据文件用LZO压缩生成.lzo文件。对.lzo文件跑一次索引工具生成.lzo.index文件。命令大致是hadoop jar /path/to/hadoop-lzo.jar com.hadoop.compression.lzo.LzoIndexer /path/to/your/data.lzo在作业里使用支持LZO索引的输入格式比如LzoTextInputFormat或LzoIndexedTextInputFormat它读取.lzo.index中的偏移量列表把文件按逻辑块切成多个split分给不同Map任务并行处理。这里的核心在于.lzo.index记录了每个压缩逻辑块在整个文件中的起始偏移量每个块的边界都是可解压的独立起点。LZO这条路在旧版本Hadoop集群里几乎是处理超大文本压缩文件的唯一正解性能损失也远小于BZip2。如果你用CDH版本自带hadoop-lzo需要的相关Jar包相对容易找Apache发行版则需要手动编译并分发到各个节点还要确保LD_LIBRARY_PATH里能找到native库。5.3 如果只能gzip/Snappy别在文件层做整包压缩如果集群里实在没有LZO组件或者上游还是坚持给gzip/Snappy有没有替代思路有但路径不在更换压缩格式上而是在改变文件组织方式上。第一种办法是上游ETS先做分片压缩按时间维度比如每5分钟一个文件或者按业务维度每台机器一个文件把数据拆成多个文件每个文件大小控制在HDFS块大小附近或者略小。这样就算每个小gzip文件只有一个Map处理Map的个数也能跟上数据量并行度不至于崩掉。我见过一个团队把按天全量的2GB gzip改为按时段拆成12个文件后作业并行度从1直接变成12耗时缩短了80%。这个改动成本极低收益却极高。第二种办法是用容器格式Parquet、ORC、SequenceFile这些格式都支持内部块级压缩而且天然具备切分能力。还是那个2GB文本数据如果用Parquet存储、内部列块用snappy压缩MapReduce读取时按文件内部的行组Row Group划分split并行度完全不受压缩影响。Parquet本身自带统计信息、谓词下推等优化比在纯文本文件上做压缩要多好几层收益。第三种办法是自定义InputFormat在代码里重写isSplitable方法对gzip文件返回true然后在createRecordReader里按字节偏移自行解压起始位置。这个方案技术上是可行的但实现复杂度高而且gzip格式没法真正从头解压任意偏移只能靠读取前一块再丢弃前半段这种暴力方案效率很低我不推荐生产环境使用只适合作为知识储备。6. 我踩过的压缩坑与应急排错思路6.1 坑1给Parquet或ORC外层再套gz压缩失效还拖慢查询有一次数据组同学建了张Hive表STORED AS PARQUET然后又觉得HDFS空间紧张在入库时给Parquet文件外层又套了一层gzip。直觉上压两遍更省空间实际效果完全不是这样。Parquet内部已经按列、按行组做了压缩外层再压一次存储空间几乎省不下多少可能只降低几个百分点反而引入两个新问题一是读取Parquet时必须先把整个外层gzip流解压才能解析内部的列式结构这部分解压开销完全浪费二是底层HDFS缓存和Parquet的footer读取、谓词下推等优化全部失效因为读取器看到的不是Parquet文件而是一个gzip压缩流。正确做法是直接在Parquet/ORC的内部压缩配置里选格式比如创建表时CREATE TABLE logs ( ... ) STORED AS PARQUET TBLPROPERTIES (parquet.compressionSNAPPY);或对已有表调整parquet.compression属性。ORC用orc.compress和orc.compress.size。只有文件格式本身不支持内部压缩的纯文本比如CSV、JSON行才需要外层文件压缩。6.2 坑2map输出压缩级别调太高导致CPU空转有一段时间我给某个MapReduce作业开中间结果压缩用了GzipCodec结果作业不仅没变快反而变慢了。查完发现原因很微妙GzipCodec默认压缩级别在Hadoop里不是低档位某些实现下Deflater的压缩级别很高Map端所有输出都要经过这层高成本压缩CPU早就打满了IO时间却没省下多少。解决方式很简单。中间结果压缩要的是速度不是极限压缩比所以选Snappy/LZ4或者用Zstd并调低压缩级别。ZStandardCodec可以通过mapreduce.map.output.compress.codec.zstd.level配置压缩级别我一般设置的区间在1到3之间实际效果是压缩速度极快压缩比也说得过去。做这件事之前最好用集群真实数据跑一个小样本什么都比不过实测。6.3 坑3Snappy/LZO原生库缺失导致启动即失败在Apache发行版Hadoop里Snappy和LZO的native支持并不总是默认带齐。有一次我在一个测试集群上配好了Snappy运行作业时直接抛java.lang.RuntimeException: native snappy library not available日志里只留一行This version of the libsnappy library is not supported...。排查链路基本是这样的先确认节点上有没有对应的native库libsnappy.so或libhadoop.so里包含的snappy实现然后看hadoop checknative的输出hadoop checknative -a这个命令会列出当前Hadoop识别到的压缩库状态非常直观。如果snappy显示FAILED多半是libsnappy没装或者Hadoop的native库目录$HADOOP_HOME/lib/native没配置到java.library.path里。LZO则通常需要额外编译hadoop-lzo包并把libgplcompression.so分发到每个节点。这里要给新手提个醒很多版本问题不在你写的代码里而在每台机器上的native库是否和Hadoop版本匹配在新扩容的DataNode上尤其容易漏。6.4 坑4CRC校验失败与不完整的压缩文件解压时遇到IOException: CRC mismatch或者invalid distance too far back这类报错第一反应别慌它大概率不是集群坏了而是文件本身写坏或者传输过程中被截断了。我遇到过一个非常典型的场景同事用自研工具生成gzip文件写入HDFS时只调用了write和flush没有调用finish()就关闭流生成的.gz文件缺少结尾标记部分解压器读到最后直接报CRC错误。排查办法是先把原始文件下载到本地跑一遍标准解压工具gzip -t file.gz如果本地也报错说明文件生成链路有问题如果本地正常但Hadoop读取报错则要怀疑DataNode上的数据块损坏。HDFS有自动校验机制hadoop fs -fsck /path/file.gz -files -blocks -locations可以看块级别状态。文件块损坏时常规做法是从其他副本恢复如果所有副本都坏了只能回到上游重新生成。这个教训告诉我们写压缩文件时务必保证finish()或close()被正确调用不要在写完之前就释放流资源。结尾一套省心的默认压缩策略在我的集群上经过整整一年反复调优和踩坑最终沉淀下来的默认策略大概是这样交互查询和数仓明细表的Parquet/ORC文件内部压缩统一用Snappy临时表和中间结果MapReduce阶段用SnappySpark用默认的LZ4离线归档和很少读取的冷数据文件层用Zstd且压缩级别调到中高或者纯文本用BZip2。这套组合让我把集群磁盘使用率从89%压到了61%作业总耗时基本没有回升。最后分享一个判断经验压缩配置永远不要人云亦云先问自己的数据可压缩性强不强、IO瓶颈在不在带宽上、下游读取频率有多高。然后拿一个作业做对照实验开压缩跑一次、不开压缩跑一次用Counter里的字节数和作业耗时做决策依据。另外一个小技巧是每次修改压缩配置后养成看hadoop checknative的习惯native库这种环境问题最容易在扩容新节点时悄悄冒出来。压缩这件事不复杂但每一步的选型背后都有代价想清楚了再动手它就会成为集群性能的放大器而不是背锅侠。
阅读完成 · 觉得有帮助?