- 《Hadoop权威指南》的
5.4
节,讲述了一些二进制文件格式:顺序文件(SequenceFile)、MapFile、Avro等 - 自己也没有特别大的体会,只能暂时先记录学到的一些知识
1. 顺序文件 —— SequenceFile
- 考虑日志文件,每一行代表一条日志记录。
- Hadoop的SequenceFile,支持二进制
key-value
对的持久化存储,适合用作日志文件的存储。 - 因为你可以使用
IntWritable
类型的时间戳,作为日志记录的key;使用Writable
类型的数据,作为日志记录的value - 除此之外,SequenceFile还可以作为小文件的容器,通过key-value形式将将若干个小文件合并到一个SequenceFile文件中。
1.1 SequenceFile的写操作
- 通过静态方法
createWriter()
,获取SequenceFile.Writer
对象
- 如下的
createWriter()
虽然已被废弃,但是非常具有代表性 - 获取
SequenceFile.Writer
对象时,需要指定FileSystem
和Path
(对应输入数据流)、Configuration
、keyClass
和valClass
对应key-value的类型 - 同时,还有可选的压缩类型、显示数据写入进度的回调函数
public static SequenceFile.Writer createWriter(FileSystem fs, Configuration conf, Path name,
Class keyClass, Class valClass, SequenceFile.CompressionType compressionType,
Progressable progress) throws IOException {
return createWriter(conf, SequenceFile.Writer.file(name), SequenceFile.Writer.filesystem(fs), SequenceFile.Writer.keyClass(keyClass), SequenceFile.Writer.valueClass(valClass), SequenceFile.Writer.compression(compressionType), SequenceFile.Writer.progressable(progress));
}
-
SequenceFile.Writer
写顺序文件的示例:
- 通过
writer.append()
方法,将key-value以顺序写的方式写入顺序文件 - 通过
writer.getLength()
获取顺序文件的当前位置,也就是写入C语言中写入指针指向的位置。
public class SequenceTest {
public static void main(String[] args) throws IOException {
String uri = "hdfs://user/hadoop/input/test.seq";
// 准备输入流的相关配置
Path path = new Path(uri);
Configuration configuration = new Configuration();
FileSystem fileSystem = FileSystem.get(URI.create(uri), configuration);
IntWritable key = new IntWritable();
Text value = new Text();
SequenceFile.Writer writer = SequenceFile.createWriter(fileSystem, configuration, path, key.getClass(), value.getClass());
for (int i = 1; i <= 10; i++) {
// 准备key-value
key = new IntWritable(i);
value = new Text(RandomStringUtils.random(8, false, true));
System.out.printf("[%d]\t%d\t%s\n", writer.getLength(), key.get(), value.toString());
// 将key-value追加到顺序文件中
writer.append(key, value);
}
// 关闭输入流
IOUtils.closeStream(writer);
}
}
1.2 顺序文件的读取
- 读取顺序文件,需要
SequenceFile.Reader
类,reader实例的创建可以直接通过SequenceFile.Reader
的构造函数实现
// 其中一种构造函数
public Reader(FileSystem fs, Path file, Configuration conf) throws IOException {
this(conf, file(file.makeQualified(fs)));
}
- 创建好
SequenceFile.Reader
实例后,反复调用next()
方法获取顺序文件中存储的key-value,就可以实现顺序文件的读取。
public synchronized boolean next(Writable key, Writable val) throws IOException
① next()
方法返回true
,表示读取到了一条key-value记录;
② 返回false
,表示读到了顺序文件的末尾,可以停止文件读取。
- 顺序文件中,一条key-value称为一条记录。
- 如果通过
seek()
方法设置文件读取的位置,很容易出现该位置不是一条记录的边界(start或end) - 顺序文件提供了同步点这一数据结构,用于标记数据流中、与记录边界同步的位置。
- 可以通过
syncSeen()
,判断当前位置是否是一个同步点
public synchronized boolean syncSeen()
- 通过
sync()
,跳到指定position
的下一个同步点。若无同步点,则跳到文件末尾
public synchronized void sync(long position) throws IOException
- 可以通过
getPosition()
获取文件的当前读取位置。
public synchronized long getPosition() throws IOException
-
SequenceFile.Writer
类,也有sync()
方法,它用于在当前位置插入同步点。
1.3 顺序文件的格式
- 顺序文件的结构如下图所示,由文件头、掺杂同步点的若干记录组成。
- 其中,文件头主要包含以下内容:
名称 | 大小 | 备注 |
SEQ | 3 byte | 顺序文件代码 |
version | 1 byte | 顺序文件的版本号 |
keyClass | 书中未提及,后续补充 | key类名 |
valueClass | 书中未提及,后续补充 | value类名 |
数据压缩类型、同步标识、用户定义的元数据等 | 这些内容都是简单提及 |
- 顺序文件支持对记录进行压缩,可以按
RECORD
或BLOCK
压缩。
- 与之前,Hadoop的压缩中,MR有专门针对顺序文件的压缩设置,决定是按记录
RECORD
压缩,还是按块BLOCK
压缩相呼应。 - 如果是按
RECORD
压缩,则是对记录中的value进行压缩:每条记录的开头处,是记录的长度。 - 如果是按
BLOCK
压缩,则是对多条记录中的key和value都进行压缩:每个块的开头处,是记录的条数;同时,每个块的开始处紧跟一个同步点。
- 从两张图中,可以看出:是否进行压缩以及按马忠压缩,顺序文件中record的组织方式都会发生变化。
2. MapFile
- 对顺序文件中的记录,按照key进行排序将得到MapFile。其具有以下特点:
- MapFile拥有索引,使得MapFile可以按key查找记录。
- 索引自身就是个顺序文件,包含了主数据文件中的一小部分key。默认情况,每隔
128
个key,创建索引。 - 索引可以加载进内存,可以提供对主数据文件的快速查找。
- MapFile的主数据文件就是按照key排序后的顺序文件。
3. 行式存储和列式存储
- 除了刚才提及的顺序文件、MapFile,面向大规模数据处理而设计的Avro文件,都采用行式存储。
- 如果将数据看作一张表,则行式存储,一行中的数据在存储介质中是连续存储的
先按列依次存储第一行中的数据,然后存储第二行中的数据。。。。以此类推 - 与之对应的是列式存储:一列中的数据,在存储介质中是连续存储的
先对行进行split(若干行为一个split),然后将每个split中的数据按列存储。
先存储split中第一列的数据,再存储第二列的数据。。。以此类推 - Hadoop中,使用列式存储的文件格式有:Hive的
ORC
文件、基于Google Dremel的Parquet
文件
行式存储与列式存储的比较:
- 行式存储适合的场景:
① 适合随机的增删改查操作(增删改查,只是局部操作,而列式存储中则是全局操作)
② 适合访问一行中大部分数据的情况
③ 需要频繁插入或更新的操作,与索引和操作范围更相关 - 列式存储适合的场景:
① 适合访问一行中少部分数据的情况,可以避免全表扫描。
② 可以针对各列进行并发运算,在内存中聚合完整的记录集,最大可能地提高查询效率
③ 每列数据独立存储,可以按列进行压缩,节省存储空间
4. 总结
关于顺序文件:
- 二进制key-value的持久存储格式,适合日志文件存储、大量小文件的合并
- 顺序文件的写:通过
createWriter()
获取SequenceFile.Writer
对象,通过append()
实现顺序写,通过getLength()
获取写入指针的当前位置 - 顺序文件的读:通过构造方法获取
SequenceFile.Reader
对象,通过next()
按记录读取顺序文件(文件末尾返回false
),通过getPosition()
获取文件的读指针的位置 - 关于同步点:
reader.syncSeen()
获取当前位置是否为同步点,reader.sync()
跳到指定position的之后的第一个同步点,writer.sync()
插入同步点 - 顺序文件的格式:文件头 + records + record中穿插的同步点;基于
RECORD
和BLOCK
压缩的顺序文件,结构差异
关于MapFile
- 带索引的、按key排序后的顺序文件
- 索引是一个顺序文件,包含主数据文件中的一小部分key(默认128个key),可以加载到内存中,提供对主数据文件的快速查找
Hadoop中常见的文件格式:
- 顺序文件、MapFile和Avro都是行式存储
- ORC和parquet都是列式存储
行式存储与列式存储
- 行式存储与列式存储的概念
- 行式存储:适合随机的增删该查、适合访问一行中的大部分数据、适合频繁的增加和删除操作
- 列式存储:适合访问一行中的少部分数据、适合按行压缩以减少存储空间、适合并发计算以提交查询效率