Hadoop I/O操作

数据完整性

HDFS数据完整性
用户希望储存和处理数据的时候,不会有任何损失或者损坏。由于每个DataNode操作磁盘或网络I/O操作可能会对正在读写的数据处理不慎而出现错误,hadoop提供了他的
数据检测方式:

主要在于两个方面:一是校验和的方法实现;二是DataBlockScanner数据块检测程序;

一:校验和
在系统启动时计算数据的校验和,在通道传输过程中,如果新生成的校验和不完全匹配原始的校

验和,那么数据就会被认为是被损坏的。

1:DataNode在读取Block时候计算checksum;

2:针对数据的每个io.bytes.per.checksum字节(这个字节是怎么来的?!),都会创建一个单独的校 验和。默认值为512字节;
3:如果计算后的checksum,与block创建时值不一样,说明该block已经损坏。
4:client读取其它DN上的block;NN标记该块已经损坏,然后复制block达到预期设置的文件备份数。
5:每个datanode都维护着一个连续的校验和和验证日志,里面有着每个block的最后验证的时间。数据端成功验证block之后,便会告诉datanode,datanode随之更新日志。

写入数据节点验证

Hdfs会对写入的所有数据计算校验和,并在读取数据时验证校验和。

元数据节点负责在验证收到的数据后,储存数据及其校验和。在收到客户端数据或复制其他datanode的数据时执行。

正在写数据的客户端将数据及其校验和发送到一系列数据节点组成的管线,管线的最后一个数据节点负责验证校验和

读取数据节点验证

客户端读取数据节点数据也会验证校验和,将它们与数据节点中储存的校验和进行比较。

每个数据节点都持久化一个用于验证的校验和日志。
客户端成功验证一个数据块后,会告诉这个数据节点,数据节点由此更新日志。

对本地文件I/O的检查

Hadoop的LocalFileSystem类是用来执行客户端的校验和验证。当写入一个名为filename的文件时文件系统客户端会在包含文件块校验和的同一目录内建立一个名为Filename.crc的隐藏文件。
LocalFileSystem类通过ChecksumFileSystem类来完成自己的任务
FileSystem rawFs;
FileSystem checksummedFs=new ChecksumFileSystem(rawFS);
可以通过CheckFileSystem的getRawFileSystem()方法获取源文件系统。
当检测到错误,CheckFileSystem类会调用reportCheckSumFailure()方法报告错误,然后LocalFileSystem将这个出错的文件和校验和移到名为bad_files的文件夹内,管理员可以定期检查这个文件夹。

二:数据块检测程序(DataBlockScanner)

每个datanode会在后台线程运行一个DataBlockScanner定期验证存储在datanode上的所有block。这是为了防止物理存储介质出现衰减。默认情况下DN在其文件创建后三周验证其checksum。由于对数据节点上的每一个数据块扫描一遍要消耗较多系统资源,因此扫描周期的值一般比较大,这就带来另一个问题,就是在一个扫描周期内可能出现数据节点重启的情况,所以为了提高系统性能,避免数据节点在启动后对还没有过期的数据块又扫描一遍,DataBlockScanner在其内部使用了日志记录器来持久化保存每一个数据块上一次扫描的时间。这样的话,数据节点可以在启动之后通过日志文件来恢复之前所有的数据块的有效时间

数据恢复

基本思路是:HDFS存储至少三个相同的数据块,假设数据块1,2,3时相同的三个数据块,

①客户端读取数据块1时,检测到数据块1发生了错误,首先向namenode报告数据块1已经损坏;并抛出异常信息;

②namenode将这个数据块1标记成已损坏,所以namenode不会再分配客户端去读取数据块1,而是分配客户端去读取数据块2或者数据块3;

③为了保证正确数据块1的数量不变,会复制数据块2或3到datanode的数据块4中;

④删除已损坏数据块1,这样就保证复本因子的不变。数据块数量为3.

数据压缩

对于任何大容量的分布式存储而言,文件压缩都是必须的,文件压缩带来的好处是:

Ⅰ:减少文件所需的存储空间;

Ⅱ:加快文件在网络上或磁盘上的传输速率;
hadoop关于文件压缩的代码几乎都在package.org.apache.hadoop.io.compress中。

(1)hadoop对压缩工具的选择

这里写图片描述

压缩一般是在时间和空间上的一种权衡。一般来说,更长的压缩时间会接生过更多的空间。不同的压缩算法之间有一定的区别。
(2)压缩分割和输入分割
  压缩分割和输入分割时很重要的。bzip2支持文件分割,用户可以分开读取每块内容并分别处理之,因此bizip2压缩的文件可分割存储
(3)在MapReduce程序中使用压缩
  在MapReduce中使用压缩非常简答,只需在它进行Job配置时配置好conf就可以了。

设置map处理后压缩数据的代码如下:
JobConf conf = new Jobconf();
conf.setBoolean(“mapred.compress.map.output”,true);
对一般情况,压缩总是好的,无论是对最终结果的压缩还是对map处理后的中间数据进行压缩。
CodeC
实现了一种压缩解压算法。Hadoop中压缩解压类实现CompressionCodec接口
createOutputStream来创建一个CompressionOutputStream,将其压缩格式写入底层的流
演示HDFS上一个1.bzip2算法压缩的文件解压,然后把解压的文件压缩成2.gz
本地库
Hadoop使用java开发,但是有些需求和操作并不适合java,所以引入了本地库 native。可以高效执行某些操作。如使用gzip压缩解压时,使用本地库比使用java时间要缩短大约10%,解压达到50%。在hadoop_home/lib/native下
在hadoop配置文件core-site.xml可以设置是否使用native

Hadoop.native.lib
true

默认是启用本地库,如果频繁使用原生库做压解压任务,可以使用codecpool,通过CodecPool的getCompressor方法获得Compressor对象,需要传入Codec 。这样可以节省创建Codec对象开销 ,允许反复使用。

数据的I/O中序列化操作

序列化是将对象转化为字节流的方法,或者说用字节流描述对象的方法。与序列化相对的是反序列化,反序列化就是将字节流转化为对象的方法。序列化有两个目的 :
进程间通信;
数据持久性存储;
hadoop采用RPC来实现进程间通信 。一般而言,RPC的序列化机制有以下特点:
紧凑:紧凑的格式可以充分利用带宽,加快传输速度;
快速:能减少序列化和反序列化的开销;
可扩展性:可以逐步改变,是客户端与服务器端直接相关 的;
互操作性:支持不同语言编写的客户端与服务器交互数据;
在hadoop中,序列化处于核心地位。因为无论是存储文件还是计算中传输数据,都需要执行序列化过程。hadoop并没有采用java提供的序列化机制(java object serialization),而是自己重新写了一个序列化机制Writeables,它具有紧凑,快速的特点,更方便。
(1)Writable类
Writable是hadoop的核心,hadoop通过它定义了hadoop中基础的数据类型和操作。一般来说,无论是 上传下载数据还是运行MapReduce程序,无时无刻不需要使用Writable类,
Writeable类中只定义了两种方法:序列化输出数据流和反序列化输入数据流;
或者这样分类:将其状态写到DataOutput二进制流(序列化)和从DataInput二进制流读取状态(发序列化);
1)Hadoop的比较器
WritableComparable是Hadoop中的接口类。
在执行MapRedure,我们知道会对key默认的排序输出,就是WritableComparable的功劳。
2)writable类中的数据类型
java基本类;例如:boolean,byte,int,float,long,double(6个)
其他类
NullWritable:这是一个占位符,他的序列化长度为0
BytesWritable和ByteWritable:BytesWritable是一个二进制数据数组的封装;ByteWritable是二进制数据封装
Text:hadoop对string类型的重写,使用标准的UTF-8编码。可以理解成Java中String类,但有一定的区别,例如索引、可变性等;
ObjectWritable:一种多类型的封装。
ArrayWritable和TwoArrayWritable:针对数组和二维数组的构建的数据类型。
MapWritable和SortedMapWritable:分别是java.util.Map()和java.util.SortedMap()的实现。
CompressedWritable:保存压缩数据的 数据结构。
GenericWritable:通用的数据封装类型。
VersionedWritable:一个抽象的版本检查类。

(2)实现自己的hadoop数据类型
hadoop可以支持实现自己的数据类型。

序列化框架Avro

尽管大部分MapReduce程序使用的是Writeable类型的key和value,但不是强制使用。可以使用任何类型,只要能有一种机制对每个类型进行类型和二进制表示来回转换。

序列性框架就是解决这种问题,它是用一个Serialization实现来表示,Avro就是一种序列化框架。

针对MapReduce的文件类

HDFS和MR主要针对大数据文件来设计,在小文件处理上效率低.解决方法是选择一个容器,将这些小文件包装起来,将整个文件作为一条记录,可以获取更高效率的储存和处理,避免多次打开关闭流耗费计算资源.hdfs提供了两种类型的容器 SequenceFile和MapFile

对于默写应用,需要特殊的数据结构来存储自己的数据。针对此需求,hadoop提供了一些更高层次的容器。

hadoop定义了一些文件数据结构以适应Mapreduce编程框架的需要,其中SequenceFile和MapFile两种类型非常重要。Map输出的中间结果就是由他们表示的,其中,MapFile是经过排序并带有索引的SequenceFile.
(1)SequenceFile类

Sequence file由一系列的二进制key/value组成,如果key为小文件名,value为文件内容,则可以将大批小文件合并成一个大文件。Hadoop-0.21.0版本开始中提供了SequenceFile,包括Writer,Reader和SequenceFileSorter类进行写,读和排序操作。该方案对于小文件的存取都比较自由,不限制用户和文件的多少,支持Append追加写入,支持三级文档压缩(不压缩、文件级、块级别)。

记录的是key/value对的列表,是序列化之后的二进制文件,因此不能直接查看,可通过命令查看文件内容。
hadoop fs -text MySequenceFile
sequence有三种不同类型的结构:
未压缩的key/value对;
记录压缩的key/value对(只有value被压缩);
Block压缩的key/value对;
注:未压缩和只压缩value的SequenceFile数据格式,两种数据格式相同。

public class SequenceFileWriteDemo { 
private static final String[] DATA = { "One, two, buckle my shoe", "Three, four, shut the door", "Five, six, pick up sticks", "Seven, eight, lay them straight", "Nine, ten, a big fat hen" }; 

public static void main(String[] args) throws IOException { 
 String uri = =“hdfs://master:8020/number.seq";
 Configuration conf = new Configuration(); 
FileSystem fs = FileSystem.get(URI.create(uri), conf); 
Path path = new Path(uri); 
IntWritable key = new IntWritable(); 
Text value = new Text(); 
SequenceFile.Writer writer = null; 
try { 
writer = SequenceFile.createWriter(fs, conf, path, key.getClass(), value.getClass()); 
for (int i = 0; i < 100; i++) { 
key.set(100 - i); 
value.set(DATA[i % DATA.length]); System.out.printf("[%s]\t%s\t%s\n", writer.getLength(), key, value); 
writer.append(key, value); 
} } 
finally { IOUtils.closeStream(writer); }
 } 
}
public class SequenceFileReadDemo {

  public static void main(String[] args) throws IOException {
    String uri = =“hdfs://master:8020/number.seq";
    Configuration conf = new Configuration();
    FileSystem fs = FileSystem.get(URI.create(uri), conf);
    Path path = new Path(uri);

    SequenceFile.Reader reader = null;
    try {
      reader = new SequenceFile.Reader(fs, path, conf);
      Writable key = (Writable)
        ReflectionUtils.newInstance(reader.getKeyClass(), conf);
      Writable value = (Writable)
        ReflectionUtils.newInstance(reader.getValueClass(), conf);
      long position = reader.getPosition();
      while (reader.next(key, value)) {
     //同步记录的边界
        String syncSeen = reader.syncSeen() ? "*" : "";
        System.out.printf("[%s%s]\t%s\t%s\n", position, syncSeen, key, value);
        position = reader.getPosition(); // beginning of next record
      }
    } finally {
      IOUtils.closeStream(reader);
    }
  }
}

(2)MapFile类

一个MapFile可以通过SequenceFile的地址,进行分类查找的格式。使用这个格式的优点在于,首先会将SequenceFile中的地址都加载入内存,并且进行了key值排序,从而提供更快的数据查找。
与SequenceFile只生成一个文件不同,MapFile生成一个文件夹。
索引模型按128个键建立的,可以通过io.map.index.interval来修改
缺点
1.文件不支持复写操作,不能向已存在的SequenceFile(MapFile)追加存储记录
2.当write流不关闭的时候,没有办法构造read流。也就是在执行文件写操作的时候,该文件是不可读取的
排序后的SequeneceFile,并且它会额外生成一个索引文件提供按键的查找.读写mapFile与读写SequenceFile
非常类似,只需要换成MapFile.Reader和MapFile.Writer就可以了。
在命令行显示mapFile的文件内容同样要用 -text

public class MapFileWriteFile
{
private static final String[] myValue={"hello world","bye world","hello hadoop","bye hadoop"};
public static void main(String[] args)
{
String uri=“hdfs://master:8020/number.map";
Configuration conf=new Configuration();
FileSystem fs=FileSystem.get(URI.create(uri),conf);
IntWritable key=new IntWritable();
Text value=new Text();
MapFile.Writer writer=null;
try
{
writer=new MapFile.Writer(conf,fs,uri,key.getClass(),value.getClass());
for(int i=0;i<500;i )
{
key.set(i);
value.set(myValue[i%myValue.length]);
writer.append(key,value);
}
finally {IOUtils.closeStream(writer);}
}
}
}
//MapFile会生成2个文件 1个名data,1个名index
//查看前10条data+index $ hdfs –fs –text /number.map/data | head
public class MapFileReadFile
{
public static void main(String[] args)
{
String uri=“hdfs://master:8020/number.map";
Configuration conf=new Configuration();
FileSystem fs=FileSystem.get(URI.create(uri),conf);
MapFile.Reader reader=null;
try
{
reader=new MapFile.Reader(fs,uri,conf);
WritableComparable key=(WritableComparable)ReflectionUtils.newInstance(reader.getValueClass(),conf);
while(reader.next(key,value))
{
System.out.printf("%s\t%s\n",key,value);
}
reader.get(new IntWritable(7),value);
System.out.printf("%s\n",value);
}
finally
{ IOUtils.closeStream(reader); }
}
}

SequenceFile文件是用来存储key-value数据的,但它并不保证这些存储的key-value是有序的,
而MapFile文件则可以看做是存储有序key-value的SequenceFile文件。
MapFile文件保证key-value的有序(基于key)是通过每一次写入key-value时的检查机制,这种检查机制其实很简单,就是保证当前正要写入的key-value与上一个刚写入的key-value符合设定的顺序,
但是,这种有序是由用户来保证的,一旦写入的key-value不符合key的非递减顺序,则会直接报错而不是自动的去对输入的key-value排序

SequenceFile转换为MapFile
mapFile既然是排序和索引后的SequenceFile那么自然可以把SequenceFile转换为MapFile使用mapFile.fix()方法把一个SequenceFile文件转换成MapFile

public static void main(String[] args)throws Exception{
Configuration conf=new Configuration();
URI uri=new URI(“hdfs://master:8020/number.map”);//uri下必须有要转换的sq文件
FileSystem fs=FileSystem.get(uri,conf);
Path map=new Path(uri.toString());
Path mapData=new Path(map,MapFile.DATA_FILE_NAME);
SequenceFile.Reader read=new SequenceFile.Reader(fs,mapData,conf);
Class keyClass=read.getKeyClass();
Class valueClass=reader.getValueClass();
read.close();
longentries=MapFile.fix(fs,map,keyClass,valueClass,false,conf);
System.out.printf(“create MapFile %s with %d entries\n”,map,entries);

}

(3)ArrayFile,SetFile和BloomMapFile
ArrayFile继承自MapFile,保存的是从Integer到value的映射关系。
SetFile继承自MapFile,同JAVA的set类似,仅仅是一个key的集合,而没有任何value。
BloomMapFile在实际使用中发挥的作用和MapFile类似,只是增加了过滤功能。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值