hadoop权威指南读书笔记
数据一致性
一般来说,hadoop的用户希望数据能够保持一致性(Integrity),但是由于hadoop的高并发性,数据被破坏的风险很高。一个用来检验数据是否被破坏的经典方法是计算校验和(checksum),常见的校验码是CRC-32,不过HDFS用了它的一个高效变本CRC-32C。
在HDFS中,每当有数据被写入时都会计算校验和,并且在它们被读取时验证校验和。每dfs.bytes-perchecksum字节(默认值是512)的数据被计算一个校验和。
存入数据时:一个datanode在从client或者其他datanode收到数据时都会计算校验和,并且最后一个datanode会验证这个校验和;一旦发现校验和不正确,客户端就会收到一个异常并进一步处理它们。
而读取数据时:clietn会计算数据的校验和并拿它们和datanode上的校验和作比较。此外,在datanode里面,一直有一个后台线程DataBlockScanner周期性地检查所有block的一致性。
由于HDFS中我们会存储block的多个副本,因此当遇到block损坏的时候,HDFS可以恢复它:当一个client读取到坏的block时,在它抛出ChecksumException之前,它会把这个block和datanode报告给namenode。然后namenode会标记这个block使得其他client不会在读取这个block并且也不会让这个block被拷贝到其他datanode上。再然后这个block的一个(正确)副本会被拷贝到一个新的datanode中,最后把这个坏的block删除。
当然,在特殊情况下,我们可能需要读取这个坏的block,因此我们可以在FileSystem调用open()之前,调用setVerifyChecksum(false)来关闭验证校验和。我们也可以在shell命令中加入-ignoreCrc参数来达到同样的目的。
此外,我们还可以用hadoop fs -checksum来找到一个文件的校验和。这样可以方便地帮我们查看两个文件是否相同。
最后我们可以用RawLocalFileSystem()和ChecksumFileSystem(fs)来得到一个不做校验和的文件系统,或者使得某个文件系统fs具有校验和功能。
压缩
Hadoop中也有很多压缩技术
简单提一下用户应该选择哪一种压缩格式(从好到坏):
- 优先采用容器文件格式例如序列文件,Avro文件,ORC文件,和Parquet文件等,它们都支持压缩和切分。
- 采用支持切分的压缩格式,例如bzip2
- 先将文件切分成chunk,然后对每个chunk压缩存储
- 最坏的就是直接存储整个文件
最后,我们还可以在MapReduce中利用压缩,不仅仅是在Reducer的Output阶段,也可以在Mapper的Output中利用压缩。
序列化
序列化(Serialization)是把结构对象转换为字节流的过程,后者被用作网络传输或者持久化存储。反序列化(Deserialization)是把字节流还原成原始结构。
在hadoop中,进程间的通信用RPC(remote procedure call)实现。而RPC中的序列化格式要求:
- 可压缩
- 速度快
- 可扩展:为了满足需求,协议经常变化,因此原来的格式要求容易修改
- 互用的(interoperable):在很多系统中,客户端可能用多种语言书写,格式也需要能够被解释编译成多种语言。
在hadoop中,提供了自己的序列化格式: Writable格式。这个格式是可压缩且速度快的,但是并不易于扩展,也无法被除Java以外的语言使用。当然,后来也诞生了一些其它的格式,例如Avro等。
对于hadoop的Writable接口,需要实现write(DataOutput out)和readFields(DataInput in)两个接口。在hadoop中,已经提供了很多writable类
Java基本类型的Writable包装
hadoop中对java的全部基本类型都包装了Writable类
注意到对于int和long都有两个包装类,分别是定长编码和变长编码。用户可以根据自己的需求选择:如果数据在整个取值空间均匀分布则选择定长编码,否则用变长编码可以节省空间,而且VIntWritable和VLongWritable采用同样的编码方法,因此可以不用一开始就提交8字节长度的数字。
String的Writable包装
Text也是一个很重要的包装类,你可以把它等效为java.lang.String。它采用UTF-8编码,由于用int来记录Text的字节数,因此最长的字符串是2GB。我们需要注意到Text和String有很大的不同:
Null的包装
NullWritable是一个特殊的类,它用来表示空,并且长度为0,它是一个不可变的单例,你可以用NullWritable.get()获得它的实例。
一般目的的包装
ObjectWritable和GenericWritable用来封装Java基本类型,String, emun,Writable, null或者这些类型的数组。它们两者之间的不同是,前者需要在存储数据之前,存储类名,这样会占用大量的空间。后者则考虑到在实际应用中,类型并不会很多,因此我们只需要提前定义好类型的静态数组,并在存储时只存储静态数组的索引就可以了。
Collections的包装
正如Java中提供了各种容器一样,hadoop中也提供了6中Collection Writable类: Array Writable, ArrayPrimitiveWritable, TwoDArrayWritable, MapWritable, SortedMapWritable, and EnumSetWritable.
#笔记##读书笔记#