大数据基础之 Parquet 文件格式解析
Parquet 文件格式深入解析
随着大数据和人工智能技术的迅猛发展,数据存储和处理的需求呈指数级增长。传统的存储方式,尤其是关系型数据库,已经无法满足现代应用的需求。特别是在处理非结构化数据(如视频、图像、日志等)时,传统方法显得力不从心。在这种背景下,Parquet 文件格式应运而生,并迅速成为大规模数据存储的事实标准。
什么是 Parquet?
Parquet 是一种开源的列式存储文件格式,专为高效存储和处理大规模数据而设计。它最初由 Apache 软件基金会开发,现已成为大数据生态系统中的重要组成部分。Parquet 的设计目标是优化数据读取性能,减少存储空间占用,并支持复杂的数据类型。
参考文章:
Parquet file format – everything you need to know! - https://data-mozart.com/parquet-file-format-everything-you-need-to-know/
Parquet 的核心优势
1. 列式存储
与传统的行式存储不同,Parquet 采用列式存储方式。这意味着数据按列而不是按行存储。对于分析型查询(OLAP),列式存储可以显著提高性能,因为查询通常只需要访问部分列,而不是整行数据。通过跳过不必要的列,Parquet 能够大幅减少 I/O 操作,从而加速查询。2. 高效的数据压缩
Parquet 支持多种压缩算法(如 Snappy、GZIP 等),并通过字典编码和运行长度编码(RLE)等技术进一步减少数据体积。例如,字典编码将重复的字符串替换为整数索引,从而节省存储空间。这种压缩机制不仅降低了存储成本,还减少了数据传输的开销。3. 与语言无关
Parquet 文件格式与编程语言无关,开发人员可以使用 Java、Python、C++ 等多种语言来读写 Parquet 文件。这种灵活性使得 Parquet 能够无缝集成到各种大数据处理框架中,如 Apache Spark、Apache Hive 和 Apache Arrow。4. 支持复杂数据类型
Parquet 不仅支持基本的数据类型(如整数、字符串等),还支持嵌套数据结构(如数组、映射等)。这使得 Parquet 非常适合存储半结构化数据(如 JSON、XML)。5. 开源与跨平台
作为开源格式,Parquet 不受任何特定供应商的锁定,用户可以在不同的平台和工具之间自由迁移数据。
Parquet 文件结构详解
Parquet 文件由以下几个部分组成:
1. 文件头(Magic Number)
Parquet 文件的开头是一个 4 字节的 Magic Number,值为PAR1,用于标识文件格式。2. 行组(Row Group)
行组是 Parquet 文件的基本存储单元,包含多个列块(Column Chunk)。每个列块存储一列的数据。3. 页脚(Footer)
页脚包含文件的元数据,如文件架构、压缩方式、行组偏移量等。页脚的长度存储在文件的最后 4 个字节中。
Parquet 文件结构图示
1. Parquet 文件整体结构
+---------------------+
| Magic Number | // 文件头,标识 Parquet 文件格式(4 字节,"PAR1")
+---------------------+
| Row Group 0 | // 行组 0,包含多个列块
| +-----------------+ |
| | Column Chunk 1 | | // 列块 1,存储一列的数据
| +-----------------+ |
| | Column Chunk 2 | | // 列块 2,存储一列的数据
| +-----------------+ |
| | ... | |
+---------------------+
| Row Group 1 | // 行组 1,包含多个列块
| +-----------------+ |
| | Column Chunk 1 | |
| +-----------------+ |
| | Column Chunk 2 | |
| +-----------------+ |
| | ... | |
+---------------------+
| ... | // 更多行组
+---------------------+
| Footer | // 页脚,包含元数据和行组偏移量
+---------------------+
| Footer Length | // 页脚长度(4 字节)
+---------------------+2. 行组(Row Group)结构
+---------------------+
| Row Group |
| +-----------------+ |
| | Column Chunk 1 | | // 列块 1,存储一列的数据
| | +-------------+ | |
| | | Page 1 | | | // 数据页 1,存储实际数据
| | +-------------+ | |
| | | Page 2 | | | // 数据页 2,存储实际数据
| | +-------------+ | |
| | ... | |
| +-----------------+ |
| | Column Chunk 2 | | // 列块 2,存储一列的数据
| | +-------------+ | |
| | | Page 1 | | |
| | +-------------+ | |
| | | Page 2 | | |
| | +-------------+ | |
| | ... | |
| +-----------------+ |
| ... | // 更多列块
+---------------------+3. 列块(Column Chunk)结构
+---------------------+
| Column Chunk |
| +-----------------+ |
| | Page 1 | | // 数据页 1,存储实际数据
| | +-------------+ | |
| | | Header | | | // 页头,包含页的元数据
| | +-------------+ | |
| | | Data | | | // 数据部分,存储压缩后的数据
| | +-------------+ | |
| +-----------------+ |
| | Page 2 | | // 数据页 2,存储实际数据
| | +-------------+ | |
| | | Header | | |
| | +-------------+ | |
| | | Data | | |
| | +-------------+ | |
| +-----------------+ |
| ... | // 更多数据页
+---------------------+4. 页(Page)结构
+---------------------+
| Page |
| +-----------------+ |
| | Header | | // 页头,包含页的元数据
| | +-------------+ | |
| | | Page Type | | | // 页类型(数据页或字典页)
| | +-------------+ | |
| | | Encoding | | | // 编码方式(如 PLAIN、RLE 等)
| | +-------------+ | |
| | | Data Size | | | // 数据部分的大小
| | +-------------+ | |
| +-----------------+ |
| | Data | | // 数据部分,存储压缩后的数据
| +-----------------+ |
+---------------------+5. 页脚(Footer)结构
+---------------------+
| Footer |
| +-----------------+ |
| | File Metadata | | // 文件元数据,包括文件架构、压缩方式等
| +-----------------+ |
| | Row Group Offset| | // 行组偏移量,记录每个行组的起始位置
| +-----------------+ |
| | Column Metadata| | // 列元数据,记录每列的统计信息
| +-----------------+ |
| | ... | | // 其他元数据
+---------------------+6. Parquet 文件示例图示
以下是一个具体的 Parquet 文件示例,包含两行数据:
+---------------------+
| Magic Number | // "PAR1"
+---------------------+
| Row Group 0 |
| +-----------------+ |
| | Column Chunk 1 | | // 用户ID [1, 2]
| +-----------------+ |
| | Column Chunk 2 | | // 用户名 ["Alice", "Bob"]
| +-----------------+ |
| | Column Chunk 3 | | // 年龄 [25, 30]
| +-----------------+ |
| | Column Chunk 4 | | // 城市 ["北京", "上海"]
| +-----------------+ |
+---------------------+
| Footer | // 文件架构、压缩方式、行组偏移量
+---------------------+
| Footer Length | // 页脚长度
+---------------------+7. Parquet 文件的 16 进制表示
以下是一个简化的 Parquet 文件的 16 进制表示:
50 41 52 31 // Magic Number: "PAR1"
15 00 15 80 // Row Group 0: Column Chunk 用户ID
15 80 15 80 // Row Group 0: Column Chunk 用户名
15 80 15 80 // Row Group 0: Column Chunk 年龄
15 80 15 80 // Row Group 0: Column Chunk 城市
15 80 15 80 // Footer: 文件架构
15 80 15 80 // Footer: 压缩方式
15 80 15 80 // Footer: 行组偏移量
15 80 15 80 // Footer: 其他元数据
15 80 15 80 // Footer: 页脚长度Parquet 文件的读写操作
以下通过 Python 的 pyarrow 库演示如何读写 Parquet 文件。
1. 写入 Parquet 文件
import pyarrow as pa
import pyarrow.parquet as pq# 定义数据
data = {
"用户ID": [1, 2, 3],
"用户名": ["Alice", "Bob", "Charlie"],
"年龄": [25, 30, 35],
"城市": ["北京", "上海", "广州"]
}
# 创建 PyArrow 表
table = pa.Table.from_pydict(data) # 将 Python 字典转换为 PyArrow 表
# 写入 Parquet 文件
pq.write_table(table, 'example.parquet') # 将表写入 Parquet 文件
2. 读取 Parquet 文件
# 读取 Parquet 文件
table = pq.read_table('example.parquet')# 转换为 Pandas DataFrame
df = table.to_pandas()
print(df)
输出结果:
用户ID 用户名 年龄 城市
0 1 Alice 25 北京
1 2 Bob 30 上海
2 3 Charlie 35 广州3. 查询优化:投影和谓词下推
Parquet 支持投影(只读取需要的列)和谓词下推(过滤不需要的行),从而优化查询性能。
# 只读取“用户名”和“城市”列
table = pq.read_table('example.parquet', columns=["用户名", "城市"])# 过滤年龄大于 30 的行
table = pq.read_table('example.parquet', filters=[("年龄", ">", 30)])
# 转换为 Pandas DataFrame
df = table.to_pandas()
print(df)
输出结果:
用户名 城市
0 Charlie 广州Parquet 文件的高级特性
1. 字典编码
对于重复值较多的列(如“城市”),Parquet 会使用字典编码来压缩数据。例如,将“北京”、“上海”、“广州”映射为整数索引,从而减少存储空间。2. 运行长度编码(RLE)
对于连续重复的值(如“年龄”),Parquet 会使用 RLE 来进一步压缩数据。例如,如果某列的值为[25, 25, 25, 30, 30],RLE 会将其编码为(25, 3), (30, 2)。3. 数据跳过
Parquet 文件的元数据记录了每个行组的最小值和最大值,查询引擎可以根据这些信息跳过不相关的行组。例如,如果查询条件为年龄 > 30,而某个行组的年龄范围为[20, 25],查询引擎可以跳过该行组,从而减少 I/O 操作。4. 分区存储
在大数据场景中,Parquet 文件通常按分区存储。例如,按日期分区存储日志数据,查询时只需读取相关分区的文件。
Parquet 的应用场景
1. 大数据分析
Parquet 是 Apache Spark、Apache Hive 等大数据工具的默认存储格式,适合用于 OLAP 场景。2. 数据湖
Parquet 是 Delta Lake、Apache Iceberg 等数据湖格式的基础,支持 ACID 事务和版本控制。3. 机器学习
Parquet 支持复杂数据类型(如数组、映射),适合存储特征工程和模型训练数据。
总结
Parquet 文件格式凭借其列式存储、高效压缩和查询优化等特性,已经成为现代数据存储的核心技术。通过本文的示例和操作细节,您可以更好地理解 Parquet 的工作原理,并将其应用到实际的数据处理任务中。无论是大数据分析、数据湖构建,还是机器学习,Parquet 都能提供卓越的性能和灵活性。未来,随着云原生技术的普及,Parquet 有望在更多场景中发挥其优势,成为数据存储和处理的首选格式。