一、行存储、列存储
假设有一张订单表 orders:
| order_id | user_id | amount | city | status |
|---|---|---|---|---|
| 1001 | U01 | 99.5 | Beijing | PAID |
| 1002 | U02 | 50.0 | Shanghai | PAID |
| 1003 | U03 | 200.0 | Beijing | UNPAID |
1.1 存储和读取区别
🟦 行存储(Row-Based):一整行的字段紧挨着,读到一行就能拿到所有列。(当满足条件需要选取一整行数据时,行查询最快)
[1001, U01, 99.5, Beijing, PAID ] ← 第1行整体连续存放
[1002, U02, 50.0, Shanghai, PAID ] ← 第2行整体连续存放
[1003, U03, 200.0, Beijing, UNPAID] ← 第3行整体连续存放
| |
| |
问题:你只要 2 列,但磁盘把 5 列全读进来了,80% 的 IO 是浪费。
🟩 列存储(Columnar):同一列的所有值紧挨着,不同列分块存放(甚至不同文件)。(当满足条件需要选取指定少数列数据时,列查询最快)
order_id 列: [1001, 1002, 1003] ← 同一列连续存放
user_id 列: [U01, U02, U03 ]
amount 列: [99.5, 50.0, 200.0]
city 列: [Beijing, Shanghai, Beijing]
status 列: [PAID, PAID, UNPAID]
| |
| |
优势:要几列读几列,IO 直接降到行存的 2/5。表越宽(列越多),收益越大。
1.2 为什么列存还能"压缩率超高"?
因为同一列的数据类型相同、取值相近,压缩算法效率惊人。指的就是ORC
| 列 | 行存放一起的数据 | 列存放一起的数据 | 压缩效果 |
|---|---|---|---|
| status | 1001,U01,99.5,Beijing,PAID,1002,U02,... | PAID,PAID,PAID,PAID,UNPAID,PAID... | 列存可用 RLE(Run-length encoding 游程编码):(PAID×4, UNPAID×1, PAID×1),压缩到几个字节 |
| city | 类型混杂 | Beijing,Shanghai,Beijing,Beijing... | 字典编码:{0:Beijing, 1:Shanghai} + [0,1,0,0...],整数比字符串小得多 |
| amount | 类型混杂 | 99.5, 50.0, 200.0, 100.5... | 位压缩 / Delta 编码:数值范围小,能用少 bit 数表示 |
实测数据:同一份数据,TextFile 行存 100GB → ORC/Parquet 列存常常压缩到 10-20GB,5-10 倍的压缩比是常态。
1.3 列存的"杀手锏":向量化 + 谓词下推
① 向量化执行(Vectorized Execution)
- 列存中同列数据类型一致,可以一次性加载一批(如 1024 个 int)到 CPU SIMD 寄存器,单条指令处理多个数据。
- 行存做不到,因为一行里有 int/string/decimal 混在一起,CPU 没法批量处理。
| |
② 谓词下推(Predicate Pushdown, PPD)
列存文件在每个列块里预先存了 min/max/Bloom Filter,可以在不读数据的情况下跳过整块。
| |
3 个块只读 1 个,IO 再降 67%。
1.4 那行存就一无是处了吗?
完全不是。行存有它无可替代的场景:
① 行存的优势场景
- OLTP 事务处理:
INSERT INTO ... VALUES (...)一次写入一整行,行存只需写一个连续区域;列存要拆成 N 列分别写到 N 个位置。 - 按主键查整行:
SELECT * FROM orders WHERE id=1001,行存一次 IO 拿到整行;列存要去 N 个列块拼回来。 - 频繁更新单条记录:行存原地改即可;列存要改 N 个列文件。
② 列存的劣势
写入慢:一行数据要拆开写到多个地方,且通常要批量积攒后落盘。
不适合点查:查一整行要重新"拼装"。
更新困难:通常只支持追加(Append-Only),更新要靠 Delta 文件 + Compaction(这就是 Delta/Iceberg/Hudi 在解决的问题)。
模式类似于HDFS中的edits/fsimage/Checkpoint:
Delta 文件(或日志文件):记录对表数据的所有增量更改(增、删、改)。每次事务提交都会生成新的增量文件。类比edits文件
基础文件(如Parquet/ORC文件):存储表数据在某个时间点的完整列式存储快照。类比fsimage文件
Compaction:将一段时间内积累的 Delta 文件合并到基础文件中,生成新的、更紧凑的基础文件,并清理旧的增量文件。这正是为了避免 Delta 文件无限增长,保证查询性能。类比Checkpoint
1.5 行存 vs 列存 对比总表
| 维度 | 行存储 | 列存储 |
|---|---|---|
| 物理布局 | 一行所有字段连续存放 | 一列所有值连续存放 |
| 读取单位 | 整行 | 单列 |
| 列裁剪 | ❌ 必须读整行 | ✅ 只读需要的列 |
| 压缩率 | 低(数据异构) | 高(数据同构,5-10×) |
| 谓词下推 | 弱 | 强(列级 min/max/Bloom) |
| 向量化执行 | 难 | 天然支持 SIMD |
| 写入性能 | ✅ 快(一次写一行) | ❌ 慢(拆成 N 列写) |
| 更新性能 | ✅ 原地更新 | ❌ 通常 Append + Compaction |
| 点查(按主键) | ✅ 快 | ❌ 需拼装多列 |
| 范围扫描 + 聚合 | ❌ 全列读取浪费 IO | ✅ 极快 |
| 典型代表 | MySQL/Oracle/PostgreSQL(堆表)、TextFile、SequenceFile、Avro | ORC、Parquet、ClickHouse、Doris、StarRocks、HBase(列族级伪列存) |
| 适用场景 | OLTP、高频写入、点查、整行读取 | OLAP、分析查询、大宽表、聚合统计 |
1.6 混合模式:PAX(行列混合)
现实中纯行/纯列都有缺陷,ORC 和 Parquet 实际上用的是 PAX(Partition Attributes Across)——折中方案:
| |
- 块间:按行切分(每个 Stripe/RowGroup 包含若干行),方便 HDFS 分块并行处理。
- 块内:按列存放,享受列存的所有优势(压缩、向量化、PPD)。
这就是为什么 ORC/Parquet 能兼具列存性能 + HDFS 分布式可分割性。
二、Hive 存储格式详解
Hive 的存储格式决定了数据在 HDFS 上以什么物理形式落盘,直接影响查询性能、压缩率、IO 开销和计算引擎兼容性。
一个简单的协作流程:你的SQL -> Hive(翻译成MapReduce作业) -> MapReduce框架调用InputFormat读取数据、调用SerDe解析成行对象 -> 执行计算 -> 调用SerDe序列化、调用OutputFormat写入数据
在Hive 本身只是"翻译器",真正读写文件的是底层的 InputFormat/OutputFormat 和 SerDe(序列化/反序列化器)。
存储格式演进路线:早期:TextFile → SequenceFile/RCFile → 列存储时代:ORC/Parquet → 数据湖格式:Delta/ICE/Hudi
2.1 Hive 支持的主流存储格式总览
| 格式 | 类型 | 是否列式 | 压缩支持 | 可分割 | 典型场景 |
|---|---|---|---|---|---|
| TextFile | 行式(默认) | ❌ | 支持(Gzip/Bzip2/LZO) | 部分支持 | 原始日志、ODS 落地、跨系统交换 |
| SequenceFile | 行式(K-V 二进制) | ❌ | 支持 | ✅ | Hadoop 内部中间数据 |
| RCFile | 行列混合(已淘汰) | ✅(早期) | 支持 | ✅ | 历史遗留 |
| ORC | 列式(Hive 原生) | ✅ | 内置(Zlib/Snappy/LZ4/Zstd) | ✅ | Hive 数仓首选(DWD/DWS/ADS) |
| Parquet | 列式(跨生态) | ✅ | 内置(Snappy/Gzip/Zstd) | ✅ | Spark/Impala/Presto 通用,湖仓首选 |
| Avro | 行式(Schema 演化友好) | ❌ | 支持 | ✅ | Kafka→Hive 入湖、Schema 频繁变更 |
| JSON/CSV | 行式(文本) | ❌ | 支持 | 部分 | 调试、外部数据导入 |
2.2 逐个格式详解
① TextFile(默认行式)
本质:纯文本文件,Hive默认格式
存储结构:行存储,每行一条记录,列之间用分隔符(默认
\001)1,Alice,login,2023-10-01 2,Bob,click,2023-10-01 3,Charlie,login,2023-10-02常见类型:CSV、JSON、TSV、日志文件
建表示例
1 2 3 4 5 6 7 8CREATE TABLE logs_text ( id INT, name STRING, action STRING, log_date Date ) ROW FORMAT DELIMITED FIELDS TERMINATED BY ' ' STORED AS TEXTFILE;数据存入:当MapReduce的Mapper开始读取第一行
1,Alice,login,2023-10-01时,TextInputFormat只是简单地将它作为一个Text对象(可理解为Java的String)读出。这里几乎没有计算开销,所以“无序列化开销”是优点。数据读取:此时数据在内存里只是一串文本
1,Alice,login,2023-10-01。Hive不知道这串文本的各个部分是什么。这时,SerDe(默认是LazySimpleSerDe)出场了。它需要根据Hive元数据(你在建表时定义的(id INT, name STRING, action STRING, log_date DATE))来“解释”这行文本。SerDe会按照你指定的分隔符(比如,)将这行文本拆分成字段数组["1", "Alice", "login", "2023-10-01"],然后逐个字段地尝试转换为目标类型(String转成INT,String转成DATE)。这个“转换”过程就是反序列化,它发生在数据读取时,而不是文件本身存储了类型信息。所以,“不存schema”导致每次查询都要做一次“文本解析+类型转换”,这是性能损耗的来源之一。
优点:
- 人类可读,易于调试
- 跨平台,兼容性极佳,几乎所有工具都支持,易导入导出
- 无序列化/反序列化开销
缺点:
无压缩,存储效率最低
- 行存储,同一行存储的数据类型不同,无法进行数据压缩
不存 schema,类型靠 Hive 元数据"解释"
- 需要按照定义的元数据类型,将文本转为元数据类型
不支持复杂类型
查询性能最差,要全表扫描。
这是由TextInputFormat的“行定位”方式决定的。它无法像ORC/Parquet那样,在文件的“头部”或“尾部”找到数据的统计信息(如最大值、最小值)或索引。
当你执行
SELECT * FROM user_log WHERE id = 100时,优化器(如果有的话)发现TextFile没有任何可以用来“跳过”部分数据的信息。InputFormat只能老老实实地从文件第一字节开始,逐行读取、切分、然后由SerDe转换成行对象,再判断id是否等于100。即使你要找的数据在最后一行,也必须读完前面所有行。这就是“全表扫描”。
可结合Gzip、Bzip2使用(系统自动价差,执行查询时自动解压),但使用这种方式,hive不会对数据进行切分,从而无法对数据进行并行操作。会导致 MapReduce 只能单 Mapper 处理整个文件。
正常情况(未压缩):假设user_log.txt有1GB,HDFS默认块大小128MB,那么这个文件会被切分成约8个块,存储在不同的DataNode上。TextInputFormat可以为每个块启动一个Mapper来并行处理。因为文本文件很容易从任意位置开始找到一行的起始点(向后扫描直到遇到换行符即可)。
Gzip压缩后:Gzip/Bzip2是整体压缩算法。为了解压
1,Alice,...这段数据,可能需要依赖前面几十KB的压缩上下文。这意味着:无法从任意点开始解压:你无法从第128MB的位置(第二个块的起始点)开始解压,因为你没有之前的压缩字典。
- 只能单线程:因此,
TextInputFormat在遇到一个Gzip压缩的.txt文件时,不会把它切成多个Split,而是将整个文件作为一个Split,交给**一个Mapper** 处理。这就完全丧失了MapReduce的并行能力,性能瓶颈极大。
- 只能单线程:因此,
使用场景:
- ODS 原始落地层
- 数据交换格式
- 小规模测试数据
② SequenceFile(行式二进制)
本质:Hadoop键值对序列化格式
存储结构:行存储,二进制格式
文件结构:
1 2 3 4Header (版本、Key/Value类名、压缩信息) Record 1: [Key长度][Value长度][Key][Value] Record 2: ... Sync Marker (用于切分)三种压缩模式:
1 2 3 4 5 6// 1. 不压缩 SequenceFile.createWriter(..., CompressionType.NONE); // 2. 记录压缩(只压缩Value) SequenceFile.createWriter(..., CompressionType.RECORD); // 3. 块压缩(多个记录一起压缩) SequenceFile.createWriter(..., CompressionType.BLOCK);Hive中使用:
1 2 3 4CREATE TABLE seq_table ( id INT, data STRING ) STORED AS SEQUENCEFILE;优点:
- 比 TextFile 高效,可分割,适合MapReduce
- 支持压缩(块压缩效率高)
- 支持二进制数据
缺点:
- 仍是行式,非列存储,分析性能有限
- 非Hive专属,优化有限
- 可读性差
使用场景:MapReduce中间数据存储
现状:已逐渐被 ORC/Parquet 替代,更多用于 MR 中间结果。
③ RCFile (Record Columnar File) - 早期的列存储尝试
设计思想:先按行分块,块内按列存储
物理结构:
1 2 3 4 5 6 7 8Row Group 1: Sync Marker Metadata Header Column 1 Data (压缩) Column 2 Data (压缩) Column 3 Data (压缩) Row Group 2: ...特点:
混合行列存储:大方向是行分块,块内是列存储
轻量级索引:每个行组的元数据
示例:
1 2 3 4 5CREATE TABLE rc_table ( user_id INT, name STRING, age INT ) STORED AS RCFILE;现状:已被ORC取代,基本淘汰
④ ORC(Optimized Row Columnar)⭐ Hive 首选
Hive 阵营的"亲儿子",由 Hortonworks 在hive 0.11版本提出,为Hive优化的极致列存储
核心特性:列存储 + 轻量级索引 + 复杂类型支持 + ACID事务
文件结构
每个ORC文件由一个或多个stripe组成,每个scripe大小250MB,stripe相当于RowGroup概念
| |
核心优化技术
三层索引体系:
层级 粒度 作用 File 级 整个文件 每列的 min/max/count,跳过整个文件 Stripe 级 ~250MB 每个 Stripe 内每列的 min/max,跳过整个 Stripe Row Group 级 10000 行 每个 Row Group 内每列的 min/max,跳过 Row Group Bloom Filter 可配置 等值查询时快速判断"一定不存在" 实现谓词下推,跳过不满足条件的数据块
高效编码:
1 2 3 4// ORC支持的编码 - Integer Column Encoding (RLE, Delta, Zigzag) - String Dictionary Encoding (字典编码) - Bit Packing (位打包)复杂类型支持:
1 2 3 4 5 6 7-- 支持struct, array, map, union CREATE TABLE nested_orc ( id INT, user STRUCT<name:STRING, age:INT>, tags ARRAY<STRING>, scores MAP<STRING, INT> ) STORED AS ORC;
4- Stream 概念
ORC 把一列拆成多个 Stream(流) 物理存储:
PRESENT Stream:位图,标记每行是否为 NULL。
DATA Stream:实际数据。
LENGTH Stream:变长字段(如 STRING)的长度。
DICTIONARY_DATA Stream:字典编码时的字典内容。
SECONDARY Stream:辅助流(如 Decimal 的小数部分)。
好处:NULL 值不占数据空间,字典和数据分离便于压缩。
ORC性能调优参数
| |
ORC ACID事务支持(Hive 3+)
| |
核心优势
- 列式存储:只读需要的列,IO 大幅降低。
- 三级索引:File / Stripe / Row Group(每 1 万行一个 index),支持谓词下推(PPD),可在不读数据的情况下用 min/max 跳过整段。
- 轻量编码:RLE(游程编码)、字典编码、Bit Packing。
- ACID 支持:Hive 3.x 事务表必须用 ORC(INSERT/UPDATE/DELETE)。
- 向量化执行:配合
hive.vectorized.execution.enabled=true,按 1024 行一批处理,性能翻倍。
⑤ Parquet(列式,跨生态)⭐ 湖仓首选
由 Twitter + Cloudera 2015-5月联合提出,Spark/Impala/Presto/Flink 原生支持,是 Delta/Iceberg/Hudi 的默认底层格式。
Parquet文件是以二进制方法存储,不可直接读取,文件中包括文件的数据和元数据,所以Parquet格式是自解析的。
存储Parquet数据时会按照Block大小设置行组的大小,一个mapper任务处理的最小单位是block,所以一行由一个mapper处理,增大任务并行度。
Parquet 文件结构
| |
Dremel 嵌套模型(Parquet 的"独门绝技")
Parquet 通过 Repetition Level(重复层级) 和 Definition Level(定义层级) 把任意嵌套结构(Struct/Array/Map)“扁平化"成列存。
示例 schema:
message User {
required int64 id;
repeated group address {
optional string city;
optional string zip;
}
}
一个用户可以有多个 address,每个 address 有 city/zip 可空。Parquet 用两个额外的整数列记录"这是第几次重复"和"嵌套到第几层是有值的”,从而精确还原结构。
ORC 也支持嵌套,但实现不如 Parquet 优雅,这也是 Parquet 在 JSON/半结构化数据场景占优的原因。
关键点:Parquet 自身没有事务能力,是靠 Delta/Iceberg/Hudi 在它之上叠加事务层实现的。而 ORC 在 Hive 3.x 是原生事务表的唯一选择。
⑥ Avro(行式,Schema 演化)
- 行式 + Schema 与数据一起存,对字段增减、类型变更非常友好。
- 常用于 Kafka → Hive 的入湖链路(配合 Schema Registry)。
- 分析性能弱于 ORC/Parquet,但灵活性强。
2.3 ORC vs Parquet(重点对比)
| 维度 | ORC | Parquet |
|---|---|---|
| 生态归属 | Hive 原生 | Spark/Impala/Presto 原生 |
| 压缩率 | 通常更高(5%-10%) | 略低,但已很优秀 |
| 查询性能(Hive) | 更快(向量化、PPD 更成熟) | 也很快 |
| 查询性能(Spark) | 良好 | 更优(Spark 默认) |
| 嵌套类型支持 | 支持,但不如 Parquet 优雅 | 原生 Dremel 模型,更强 |
| ACID 事务 | ✅ Hive 事务表必须 ORC | ❌ 不支持 Hive 事务 |
| 索引 | File/Stripe/RowGroup 三级 + Bloom | RowGroup + Page 统计 |
| 湖仓兼容 | Iceberg 支持 | Delta/Iceberg/Hudi 默认 |
① 建表对比
ORC 建表(Hive 推荐)
| |
Parquet 建表(Spark / 多引擎推荐)
| |
② 结构对应关系
| Parquet | ORC | 含义 |
|---|---|---|
| File | File | 整个文件 |
| Row Group(128MB) | Stripe(250MB) | 并行处理基本单位 |
| Column Chunk | Column(在 Stripe 内) | 一列在一个并行单元的数据 |
| Page(1MB) | Row Group(10000 行) | 编码压缩 + 索引最小单位 |
| Page Header 统计 | Row Index | 行级粒度索引 |
| Column Chunk 统计 | Stripe-level Statistics | 块级索引 |
| File Metadata | File Footer | 文件级元数据 |
③ 索引与谓词下推(PPD)
ORC 的索引能力(更强)
查询: SELECT * FROM orders
WHERE amount > 1000 AND city = 'Beijing'
ORC 的过滤路径:
- File Footer:看整个文件的
amount.max,如果 < 1000 直接跳过。 - Stripe Statistics:逐个 Stripe 看
amount.max,过滤掉。 - Row Group Index(每 10000 行):精确到 10000 行粒度过滤。
- Bloom Filter:对
city = 'Beijing'直接判断"该 Row Group 是否可能包含 Beijing"。 - 读取数据:上面层层过滤后只剩很少的数据需要真正解码。
Parquet 的索引能力
- 传统 Parquet:只有 Row Group 级和 Column Chunk 级的统计(min/max/null_count)。
- Parquet 2.x 新增 Page Index:补齐了 Page 级(≈ ORC 的 Row Group 级)索引。
- Bloom Filter:Parquet 1.12+ 才支持(晚于 ORC)。
结论:在 Hive 引擎下,ORC 的谓词下推历史更成熟、过滤更精细;但 Parquet 新版本已经追上,且在 Spark/湖仓生态中表现优秀。
④ 编码与压缩技术
1) 编码(Encoding)—— 减少数据本身的体积
| 编码方式 | 原理 | 适用列 | ORC | Parquet |
|---|---|---|---|---|
| RLE(游程编码) | AAAABBC → (A,4)(B,2)(C,1) | 重复值多的列(如 status、gender) | ✅ | ✅ |
| 字典编码(Dictionary) | 用整数 ID 代替字符串,建一张字典 | 低基数字符串(如 city、product_name) | ✅ | ✅ |
| Bit Packing(位压缩) | 用最少 bit 表示数值(如 0-100 只需 7 bit) | 小范围整数 | ✅ | ✅ |
| Delta 编码 | 存差值(如 时间戳、自增 ID) | 单调/近单调数值 | ✅ | ✅ |
| Run Length + Dictionary 混合 | 字典 + 游程 | 高重复低基数 | ✅ | ✅ |
| FOR(Frame of Reference) | 存"基准值 + 偏移" | 范围集中的数值 | ✅(变种) | ❌ |
2) 压缩(Compression)—— 在编码基础上再压一层
| 压缩算法 | 压缩比 | 速度 | ORC 默认 | Parquet 默认 | 适用场景 |
|---|---|---|---|---|---|
| None | 1× | 极快 | - | - | 测试 |
| Snappy | 2-3× | 快 | 可选 | ✅ 默认 | 平衡型,查询友好 |
| Zlib (Gzip) | 3-4× | 中等 | ✅ 默认 | 可选 | 存储成本敏感 |
| LZ4 | 2× | 极快 | 可选 | 可选 | 实时查询 |
| Zstd | 3-5× | 快 | 可选 | 可选 | 新选择,压缩比+速度俱佳 |
| Brotli | 4-5× | 慢 | ❌ | 可选 | 冷数据归档 |
实战推荐:现代生产环境优先用 ZSTD,压缩比接近 Zlib,速度接近 Snappy。
三、Hive 压缩格式
核心理解:Hive 不自己实现压缩,它复用 Hadoop 的压缩 Codec 体系。所以 Hive 压缩 = Hadoop 压缩 + MapReduce 压缩参数 + Hive 自己的开关。
3.1 Hive 中的压缩格式(总览)
Hive 中常见的压缩格式有 6 种,决策核心是三个维度:压缩率、压缩/解压速度、是否可切分(splittable)。
| 压缩格式 | 扩展名 | 压缩率 | 压缩速度 | 解压速度 | 是否可切分 | Hadoop 原生支持 |
|---|---|---|---|---|---|---|
| DEFLATE | .deflate | 中 | 中 | 中 | ❌ | ✅ |
| Gzip | .gz | 高 | 慢 | 中 | ❌ | ✅ |
| Bzip2 | .bz2 | 极高 | 极慢 | 慢 | ✅ | ✅ |
| LZO | .lzo | 中 | 快 | 快 | ✅(需建索引) | ❌(需额外安装) |
| Snappy | .snappy | 中 | 极快 | 极快 | ❌ | ❌(需额外安装) |
| LZ4 | .lz4 | 中 | 极快 | 极快 | ❌ | ✅ |
| Zstd | .zst | 高 | 快 | 快 | ❌ | ✅(新版本) |
📌 关键认知:
- 是否可切分 决定了一个大文件能否被多个 Map 并行处理 → 大数据场景下极其重要。
- Gzip / Snappy 不可切分,所以大文件用 Gzip 会丢掉并行度。
- 解决方式:用 ORC / Parquet 这种自带分块的列式存储 + 内部 Snappy/Zlib 压缩(块级别可切分)。
3.2 MR 支持的压缩格式 & Hadoop 压缩类(Codec)
输入压缩:MR自动识别并解压(通过InputFormat自动处理)
输出压缩:需显式配置
| |
MapReduce 通过 CompressionCodec 接口 调用各种压缩算法,每种格式对应一个 Codec 类。
Hadoop 编码/解码器类(必背)
| 压缩格式 | 对应的 Codec 类 | |
|---|---|---|
| DEFLATE | org.apache.hadoop.io.compress.DefaultCodec | |
| Gzip | org.apache.hadoop.io.compress.GzipCodec | Java自带,无需Native库 |
| Bzip2 | org.apache.hadoop.io.compress.BZip2Codec | 纯Java实现,支持分片 |
| LZO | com.hadoop.compression.lzo.LzopCodec | 需单独安装,支持索引创建 |
| Snappy | org.apache.hadoop.io.compress.SnappyCodec | 需Native库(libsnappy.so),性能极佳 |
| LZ4 | org.apache.hadoop.io.compress.Lz4Codec | 需Native库,超高速压缩 |
| Zstd | org.apache.hadoop.io.compress.ZStandardCodec |
📌 这些类的作用:告诉 MR / Hive 用什么算法去压缩或解压数据。
Native库检查命令:
| |
3.3 压缩性能比较(经典对比数据)
以一个 ~8.3 GB 文本文件压缩为例(业内常引用的对比):
| 算法 | 压缩后大小 | 压缩比 | 压缩速度 | 解压速度 |
|---|---|---|---|---|
| Gzip | 1.8 GB | 78% | 17.5 MB/s | 58 MB/s |
| Bzip2 | 1.1 GB | 86% | 2.4 MB/s | 9.5 MB/s |
| LZO | 2.9 GB | 65% | 49.3 MB/s | 74.6 MB/s |
| Snappy | ~2.2 GB | 73% | 250+ MB/s | 500+ MB/s |
选型口诀(🔥面试高频)
| 业务场景 | 推荐 |
|---|---|
| 高压缩比的存储优化(冷数据/归档) | Bzip2 / Gzip |
| 低CPU开销的计算优化(Shuffle 中间数据) | Snappy / LZ4 |
| 支持分片+较好压缩比的平衡选择(大日志文本) | LZO(带索引)/ Bzip2 |
| 现代综合最优 | Zstd |
📌 结论:
- I/O 密集型作业 → 加压缩往往提速(少传输 = 快)。
- CPU 密集型作业 → 压缩反而拖慢(CPU 已满,再压缩没资源)。
3.4 Hadoop 压缩参数(必须熟记)
Hadoop 中压缩可发生在 三处:输入端、Map 输出端、Reduce 输出端。
① 输入端(通常自动识别扩展名解压)
在core-site.xml中配置如下(Hadoop通过扩展名判断是否支持某种编解码器)
| |
② Map 输出端压缩参数(Shuffle 优化的关键)
| |
③ Reduce 输出端(最终落盘)压缩参数
| |
📌 type 参数:
NONE:不压缩RECORD:按记录压缩(压缩率低)BLOCK:按块压缩(推荐,压缩率高,仅作用于 SequenceFile)
3.5 Hive 在 Map 端 / Reduce 端的压缩方式
Hive 在 Hadoop 参数上又封装了两个自己的开关,必须先打开 Hive 的开关,再设置 Hadoop 参数才生效。
① Map 端压缩(中间结果,影响 Shuffle)
| |
👉 目的:减少 Map → Reduce 之间的网络传输和磁盘 IO。
👉 推荐:Snappy / LZ4(压缩快、解压快,CPU 开销低)。
② Reduce 端压缩(最终输出到 HDFS)
| |
👉 目的:节省 HDFS 存储空间。
👉 推荐:Gzip / Zstd / Bzip2(压缩率优先)。
③ Hive 压缩流程图(建议背下来)
| |
3.6 与 Hive 存储格式(ORC / Parquet)的配合
Hive 真实生产中,压缩往往和列式存储格式绑定使用:
| 存储格式 | 默认压缩 | 可选压缩 |
|---|---|---|
| TextFile | 无 | Gzip / Bzip2 / Snappy |
| SequenceFile | RECORD | RECORD / BLOCK |
| ORC | Zlib | Zlib / Snappy / LZO / Zstd |
| Parquet | Snappy | Snappy / Gzip / LZO / Zstd |
📌 建表时指定压缩示例:
| |
📌 重要认知:
- ORC / Parquet 天然按块压缩 → 即使用 Snappy / Gzip 这种不可切分算法,仍然可以并行处理(因为切分发生在 stripe / row group 级别)。
- 所以列式存储 + Snappy 是当前数仓的事实标准组合。