Data Integrity
HDFS transparently checksums all data written to it and by default verifies checksums when reading data. A separate checksum is created for every io.bytes.per.checksum bytes of data. The default is 512 bytes, and since a CRC-32 checksum is 4 bytes long, the storage overhead is less than 1%.
关于数据一致性, Hadoop通过CRC-32的校验和来保证
Compression
Compression and Input Splits
关注一下这个问题, 并不是每一种压缩都支持splits, 比如gzip就不支持, 知道gzip的原理就知道, 他通过指针指向前面已经出现过的数据, 所以单独给你一块, 你无法解压.
Serialization
Serialization is the process of turning structured objects into a byte stream for transmission over a network or for writing to persistent storage. Deserialization is the reverse process of turning a byte stream back into a series of structured objects.
序列化问题倒是比较重要的, 因为分布式计算, node之间的数据传输都需要序列化和反序列化的过程.
Hadoop uses its own serialization format, Writables, which is certainly compact and fast, but not so easy to extend or use from languages other than Java.
The Writable Interface
The Writable interface defines two methods: one for writing its state to a DataOutput binary stream, and one for reading its state from a DataInput binary stream:
package org.apache.hadoop.io; import java.io.DataOutput; import java.io.DataInput; import java.io.IOException; public interface Writable { void write(DataOutput out) throws IOException; void readFields(DataInput in) throws IOException; }
这个抽象接口, 到很容易理解, 序列化和反序列化就是, 把类型数据输出成二进制流, 再从二进制流将类型数据恢复出来.
可见Hadoop提供了很全的类型封装,
Java primitive Writable implementation Serialized size (bytes)
boolean BooleanWritable 1
byte ByteWritable 1
int IntWritable 4
VIntWritable 1–5
float FloatWritable 4
long LongWritable 8
VLongWritable 1–9
double DoubleWritable 8
String Text
byte[] BytesWritable
NullWritable 0, used as a placeholder, a key or a value can be declared as a NullWritable when you don’t need to use that position
ObjectWritable General-purpose wrapper, useful when a field can be of more than one type. 问题是浪费空间, writes the classname of the wrapped type every time it is serialized
GenericWritable 用于优化ObjectWritable, 当可能的类型比较少, 我们又可以预先知道, 定义static array of types, 这样序列化的时候只需要记下array index, 而不用记录class name
Arrays ArrayWritable
TwoDArrayWritable two-dimensional arrays
Map MapWritable java.util.Map<Writable, Writable>
SortedMap SortedMapWritable java.util.SortedMap<WritableComparable, Writable>
细心点会发现, 里面没有对set和list经行封装, 所以怎么表示这两种结构?
Conspicuous by their absence are Writable collection implementations for sets and lists.
A set can be emulated by using a MapWritable (or a SortedMapWritable for a sorted set), with NullWritable values.
For lists of a single type of Writable, ArrayWritable is adequate,
but to store different types of Writable in a single list, you can use GenericWritable to wrap the elements in an ArrayWritable. Alternatively, you could write a general ListWritable using the ideas from MapWritable.
WritableComparable and Comparators
WritableComparable, 顾名思义, 就是在序列化的基础上, 还能支持sort, 这个很有用, 由于hadoop需要对key进行排序, 所以所有的key都必须是WritableComparable类型.
所以对于Writable, 需要多实现一个compareTo function.
但当Hadoop经行shuffle sort时, 需要sort大量的中间结果, 所以为了sort, 需要把byte stream反序列化成类型, 然后sort结束再序列化成byte stream. 这样当数据量很大时, 效率会比较低, 所以可以定义Comparators来进行优化.
This interface permits implementors to compare records read from a stream without deserializing them into objects, thereby avoiding any overhead of object creation.
这样就避免了反序列化和序列化的overhead, 但是有个问题是, 并不是所有的类型都可以同bytes steaming进行sort的, 所以不是所有的WritableComparable都可以用compactors进行优化.
package org.apache.hadoop.io; import java.util.Comparator; public interface RawComparator<T> extends Comparator<T> { public int compare(byte[] b1, int s1, int l1, byte[] b2, int s2, int l2); }
Serialization Frameworks
Although most MapReduce programs use Writable key and value types, this isn’t mandated by the MapReduce API. In fact, any types can be used; the only requirement is that there be a mechanism that translates to and from a binary representation of each type.
上面说了, Writable是不错, 不过有个问题就是无法用于Java以外的语言, 所以需要有其它的serialization framework
Avro (http://avro.apache.org/docs/current/index.html)
Apache Avro# is a language-neutral data serialization system.
The project was created by Doug Cutting (the creator of Hadoop) to address the major downside of Hadoop Writables: lack of language portability.
Having a data format that can be processed by many languages (currently C, C++, Java, Python, and Ruby) makes it easier to share datasets with a wider audience than one tied to a single language. It is also more futureproof, allowing data to potentially outlive the language used to read and write it.
Avro就用于解决上面的语言扩展性问题...
与Avro类似的二进制序列化framework, 还有Thrift和Protocol buffer, 提供通讯数据序列化的功能和RPC服务.
区别, 或者说Avro的特点在底下这个比较blog里面说的比较清楚
Apache Avro 与 Thrift 比较
http://www.tbdata.org/archives/1307
Thrift是一个面向编程的系统, 完全依赖于IDL->Binding Language的代码生成。 Schema也“隐藏”在生成的代码中了,完全静态。为了让系统识别处理一个新的数据源,必须走编辑IDL,代码生成,编译载入的流程。
可见, Thrift是种传统的序列化的方案, 比如把java类进行序列化, 那么类对象的schema本身就包含在类定义里面, 所以在序列化的时候, 需要把Java的类定义代码转化为IDL格式, 并保存为schema文件.
IDL, 接口定义语言, 可以看成一种伪码, 可以简单的和其他各种编程语言进行互相转换
然后把类对象中包含的数据进行序列化, 结果如下, 每个Field data前面都是需要带Tag的,这个Tag用于标识这个域的类型和顺序ID(IDL中定义,用于Versioning)。在同一批数据里面,这些Tag的信息是完全相同的,当数据条数大的时候这显然就浪费了。
然后当反序列化的时候, 我们首先要有个code generation的过程, 把IDL文件翻译成目标语言代码的过程, 然后再去读数据, 并用生成的类代码产生相应的类对象, 完成反序列化的过程.
而Avro在支持Thrift的这种方法的同时, 还提出另外一种更为灵活的方案...
Avro-specific方式(Thrift的方式相似),依赖代码生成产生特定的类,并内嵌JSON Schema.
Avro-generic方式支持Schema的动态加载,用通用的结构(map)代表数据对象,不需要编译加载直接就可以处理新的数据源。
首先Avro也是可以支持Thrift那样的静态序列化的方式的, Avro-specific方式
Avro-generic方式和Thrift最大的不同是, 它的schema以Json格式存储, 这样便于将schema也放在data file中(开头位置), 如下图.
在反序列化的时候, 我们不需要先得到IDL, 再产生code, 然后在读数据.
而是可以直接从data file中读出schema, 并根据schema动态加载后面的数据.
Avro规定一个标准的序列化的格式,即无论是文件存储还是网络传输,数据的Schema(in JSON)都出现在数据的前面。数据本身并不包含任何Metadata(Tag).
在文件储存的时候,schema出现在文件头中。在网络传输的时候Schema出现在初始的握手阶段.
这样的好处一是使数据self describe,提高了数据的透明度和可操作性,二是减少了数据本身的信息量提高存储效率,可谓一举二得了
总结, Avro除了具备, 跨多语言, 支持RPC, 二进制格式...还有如下特色,
Avro provides functionality similar to systems such as Thrift, Protocol Buffers, etc. Avro differs from these systems in the following fundamental aspects.
- Dynamic typing: Avro does not require that code be generated. Data is always accompanied by a schema that permits full processing of that data without code generation, static datatypes, etc. This facilitates construction of generic data-processing systems and languages.
- Untagged data: Since the schema is present when data is read, considerably less type information need be encoded with data, resulting in smaller serialization size.
- No manually-assigned field IDs: When a schema changes, both the old and new schema are always present when processing data, so differences may be resolved symbolically, using field names.
File-Based Data Structures
SequenceFile是一种<key,value>的二进制的文件格式, 因为如果将<key,value>以文件格式存放的话, 必然会导致时空效率问题.
所以Hadoop在存放map的中间结果时都是使用SequenceFile来存放<key,value>的, SequenceFile还有另外一个用处就是, 将多个小文件组合成单个文件.
但它的问题是, 首先只支持Java, 其他语言开发Hadoop就无法使用
Avro就可以解决这个问题, 跨语言的, 而且结构更为紧凑, 而且附带schema数据可以自解释, 而且可以存放除<key,value>以外的其他类型
MapFile is a sorted SequenceFile with an index to permit lookups by key. MapFile can be thought of as a persistent form of java.util.Map (although it doesn’t implement this interface), which is able to grow beyond the size of a Map that is kept in memory.
浅析Hadoop文件格式
http://www.infoq.com/cn/articles/hadoop-file-format
1 SequenceFile
SequenceFile是Hadoop API 提供的一种二进制文件,它将数据以<key,value>的形式序列化到文件中。这种二进制文件内部使用Hadoop 的标准的Writable 接口实现序列化和反序列化。它与Hadoop API中的MapFile 是互相兼容的。Hive 中的SequenceFile 继承自Hadoop API 的SequenceFile,不过它的key为空,使用value 存放实际的值, 这样是为了避免MR 在运行map 阶段的排序过程。如果你用Java API 编写SequenceFile,并让Hive 读取的话,请确保使用value字段存放数据,否则你需要自定义读取这种SequenceFile 的InputFormat class 和OutputFormat class。
图1:Sequencefile 文件结构
3 Avro
Avro是一种用于支持数据密集型的二进制文件格式。它的文件格式更为紧凑,若要读取大量数据时,Avro能够提供更好的序列化和反序列化性能。并且Avro数据文件天生是带Schema定义的,所以它不需要开发者在API 级别实现自己的Writable对象。最近多个Hadoop 子项目都支持Avro 数据格式,如Pig 、Hive、Flume、Sqoop和Hcatalog。
图3:Avro MR 文件格式
4. 文本格式
除上面提到的3种二进制格式之外,文本格式的数据也是Hadoop中经常碰到的。如TextFile 、XML和JSON。 文本格式除了会占用更多磁盘资源外,对它的解析开销一般会比二进制格式高几十倍以上,尤其是XML 和JSON,它们的解析开销比Textfile 还要大,因此强烈不建议在生产系统中使用这些格式进行储存。 如果需要输出这些格式,请在客户端做相应的转换操作。 文本格式经常会用于日志收集,数据库导入,Hive默认配置也是使用文本格式,而且常常容易忘了压缩,所以请确保使用了正确的格式。另外文本格式的一个缺点是它不具备类型和模式,比如销售金额、利润这类数值数据或者日期时间类型的数据,如果使用文本格式保存,由于它们本身的字符串类型的长短不一,或者含有负数,导致MR没有办法排序,所以往往需要将它们预处理成含有模式的二进制格式,这又导致了不必要的预处理步骤的开销和储存资源的浪费。