Hadoop IO

本文详细介绍了Hadoop的数据完整性机制,包括CRC校验、数据节点的数据块检测以及客户端的校验和验证。此外,还讨论了Hadoop中的压缩技术,强调其减少存储空间和提升数据传输效率的作用。同时,文章提到了序列化、基本文件类型如SequenceFile和MapFile的读写操作,以及如何在MapReduce中使用压缩。
摘要由CSDN通过智能技术生成

 

总结:

     本章包含了以下内容

     第一,数据完整性,hadoop采用CRC来检测数据是否是完整的,在写入文件时,hdfs为每个数据块都生成一个crc文件。客户端读取数据时生成一个crc与数据节点存储的crc做比对,如果不匹配则说明数据已经损坏了。数据节点在后台运行一个程序定期检测数据,防止物理存储介质中位衰减而造成的数据损坏。

     第二,压缩和解压,在mapreduce中使用压缩可以减少存储空间和加速在数据在网络上的传输。

     第三,序列化,hadoop使用自己的序列化机制,实现Writable接口

     第四,基本文件类型,SequenceFile和MapFile的读写。

 

 

1 数据完整性

    由于每个磁盘或者网络上的I/O操作可能会对正在读写的数据不慎引入错误,如果通过的数据流量非常大,数据发生损坏的几率很高。

    检查损坏数据的常用方法是在第一次进入系统时计算数据的校验和,然后只要数据不是在一个可靠的通道上传输,就可能会发生损坏。如果新生成的校验和不完全匹配原始的校验和,那么数据就会被认为是损坏的。

    一个常用的错误检测代码是CRC-32(cyclic redundancy check,循环冗余检查),计算一个32位的任何大小输入的整数校验和。

1.1 HDFS的数据完整性

    HDFS以透明方式校验所有写入它的数据,并在默认设置下,会在读取数据时验证校验和。针对数据的每个io.bytes.per.checksum(默认512字节)字节,都会创建一个单独的校验和。

 

   数据节点负责在存储数据及其校验和之前验证它们收到的数据。 从客户端和其它数据节点复制过来的数据。客户端写入数据并且将它发送到一个数据节点管线中,在管线的最后一个数据节点验证校验和。

   客户端读取数据节点上的数据时,会验证校验和,将其与数据节点上存储的校验和进行对比。每个数据节点维护一个连续的校验和验证日志,因此它知道每个数据块最后验证的时间。

 

    每个数据节点还会在后台线程运行一个DataBlockScanner(数据块检测程序),定期验证存储在数据节点上的所有块,为了防止物理存储介质中位衰减锁造成的数据损坏。

  

    HDFS通过复制完整的副本来产生一个新的,无错的副本来“治愈”哪些出错的数据块。工作方式:如果客户端读取数据块时检测到错误,抛出Checksum Exception前报告该坏块以及它试图从名称节点中药读取的数据节点。名称节点将这个块标记为损坏的,不会直接复制给客户端或复制该副本到另一个数据节点。它会从其他副本复制一个新的副本。

 

    使用Open方法来读取文件前,通过setVerifyChecksum()方法来禁用校验和验证。

1.2 本地文件系统

    Hadoop的本地文件系统执行客户端校验。意味着,在写一个名为filename的文件时,文件系统的客户端以透明的方式创建一个隐藏的文件.filename.crc。在同一个文件夹下,包含每个文件块的校验和。

    数据块大小由io.bytes.per.checksum属性控制,块的大小作为元数据存储在.crc文件中。

   

    也可能禁用校验和:底层文件系统原生支持校验和。这里通过RawLocalFileSystem来替代LocalFileSystem完成。要在一个应用中全局使用,只需要设置fs.file.impl值为org.apache.hadoop.fs.RawLocalFileSystem来重新map执行文件的URL。或者只想对某些读取禁用校验和校验。

    Configuration conf = ...
    FileSystem fs = new RawLocalFileSystem();
    fs.initialize(null, conf);

   

1.2.3 ChecksumFileSystem

    LocalFileSystem使用ChecksumFileSystem(校验和文件系统)为自己工作,这个类可以很容易添加校验和功能到其他文件系统中。因为ChecksumFileSystem也包含于文件系统中。

   FileSystem rawFs = ... 
   FileSystem checksummedFs = new ChecksumFileSystem(rawFs);

 

 

 

2 压缩

    文件压缩两大好处:减少存储文件所需要的空间且加快了数据在网络上或从磁盘上或到磁盘上的传输速度。   

未命名

                                                                                    压缩格式

2.1 编码和解码

    编码和解码器用以执行压缩解压算法。在Hadoop中,编码和解码是通过一个压缩解码器接口实现的。

 

   

未命名

 

    CompressionCodec对流进行压缩和解压缩

 

    CompressionCodec有两个方法轻松地压缩和解压数据。使用use the  createOutputStream(OutputStream out)创建一个CompressionOutputStream,将其以压缩格式写入底层的流。使用createInputStream(InputStream in) 获取一个CompressionInputStream,从底层的流读取未压缩的数据。

 

   

0 1 package com.laos.hadoop;
0 2
0 3 import org.apache.hadoop.conf.Configuration;
0 4 import org.apache.hadoop.io.IOUtils;
0 5 import org.apache.hadoop.io.compress.CompressionCodec;
0 6 import org.apache.hadoop.io.compress.CompressionOutputStream;
0 7 import org.apache.hadoop.util.ReflectionUtils;
0 8
0 9 public class StreamCompressor {
10     public static void main(String[] args) throws Exception {
11         String codecClassname = "org.apache.hadoop.io.compress.GzipCodec";
12         Class < ? > codecClass = Class.forName(codecClassname);
13         Configuration conf = new Configuration();
14         CompressionCodec codec = (CompressionCodec) ReflectionUtils
15                 .newInstance(codecClass, conf);
16          //将读入数据压缩至System.out
17         CompressionOutputStream out = codec.createOutputStream(System.out);
18         IOUtils.copyBytes(System.in, out, 4096, false);
19         out.finish();
20     }
21
22 }

 

$ echo "Test"|hadoop jar hadoop-itest.jar com.laos.hadoop.StreamCompressor|gunzip

 

    使用CompressionCodecFactory方法来推断CompressionCodec

 

    在阅读一个压缩文件时,我们可以从扩展名来推断出它的编码/解码器。以.gz结尾的文件可以用GzipCodec来阅读。CompressionCodecFactory提供了getCodec()方法,从而将文件扩展名映射到相应的CompressionCodec。

 

     

0 1 package com.laos.hadoop;
0 2
0 3 import java.io.InputStream;
0 4 import java.io.OutputStream;
0 5 import java.net.URI;
0 6
0 7 import org.apache.hadoop.conf.Configuration;
0 8 import org.apache.hadoop.fs.FileSystem;
0 9 import org.apache.hadoop.fs.Path;
10 import org.apache.hadoop.io.IOUtils;
11 import org.apache.hadoop.io.compress.CompressionCodec;
12 import org.apache.hadoop.io.compress.CompressionCodecFactory;
13
14 public class FileDecompressor {
15     public static void main(String[] args) throws Exception {
16         String uri = args[0];
17         Configuration conf = new Configuration();
18         FileSystem fs = FileSystem.get(URI.create(uri), conf);
19
20         Path inputPath = new Path(uri);
21         CompressionCodecFactory factory = new CompressionCodecFactory(conf);
22         CompressionCodec codec = factory.getCodec(inputPath);
23         if (codec == null) {
24             System.err.println("No codec found for " + uri);
25             System.exit(1);
26         }
27         String outputUri = CompressionCodecFactory.removeSuffix(uri, codec
28                 .getDefaultExtension());
29         InputStream in = null;
30         OutputStream out = null;
31         try {
32             in = codec.createInputStream(fs.open(inputPath));
33             out = fs.create(new Path(outputUri));
34             IOUtils.copyBytes(in, out, conf);
35         } finally {
36             IOUtils.closeStream(in);
37             IOUtils.closeStream(out);
38         }
39     }
40 }

    CompressionCodecFactory从io.compression.codecs配置属性定义的列表中找到编码和解码器,默认情况下,Hadoop给出所有的编码和解码器。每个编码/加码器都知道默认文件扩展名。

 

   

未命名

                 

2.2 压缩和输入分隔

    考虑如何压缩哪些将由MapReduce处理的数据时,考虑压缩格式是否支持分隔很重要。

    例如,gzip格式使用default来存储压缩过的数据,default将数据作为一系列压缩过的块存储,但是每块的开始没有指定用户在数据流中的任意点定位到下一个块的起始位置,而是自身与数据同步,所以gzip不支持分隔机制。

 

2.3 在MapReduce中使用压缩

    如果要压缩MapReduce作业的输出,设置mapred.output.compress为true,mapred.output.compression.codec属性指定编码解码器。

   如果输入的文件时压缩过的,MapReduce读取时,它们会自动解压,根据文件扩展名来决定使用那一个压缩解码器。

 

  

0 1 package com.laos.hadoop;
0 2
0 3 import java.io.IOException;
0 4
0 5 import org.apache.hadoop.fs.Path;
0 6 import org.apache.hadoop.io.IntWritable;
0 7 import org.apache.hadoop.io.Text;
0 8 import org.apache.hadoop.io.compress.CompressionCodec;
0 9 import org.apache.hadoop.io.compress.GzipCodec;
10 import org.apache.hadoop.mapred.FileInputFormat;
11 import org.apache.hadoop.mapred.FileOutputFormat;
12 import org.apache.hadoop.mapred.JobClient;
13 import org.apache.hadoop.mapred.JobConf;
14
15
16 public class MaxTemperatureWithCompression {
17      public static void main(String[] args) throws IOException {
18          if (args.length != 2) {
19          System.err.println("Usage: MaxTemperatureWithCompression < input path > " +
20          " < output path >");
21          System.exit(-1);
22          }
23         
24          JobConf conf = new JobConf(MaxTemperatureWithCompression.class); conf.setJobName("Max temperature with output compression");
25          FileInputFormat.addInputPath(conf, new Path(args[0]));
26          FileOutputFormat.setOutputPath(conf, new Path(args[1]));
27         
28          conf.setOutputKeyClass(Text.class);
29          conf.setOutputValueClass(IntWritable.class);
30         
31          conf.setBoolean("mapred.output.compress", true);
32          conf.setClass("mapred.output.compression.codec", GzipCodec.class,
33          CompressionCodec.class);        
34          JobClient.runJob(conf);
35          }
36 }

 

 

 

 

3 序列化

    序列化:将结构化对象转换为字节流以便于通过网络进行传输或写入存储的过程。

    反序列化:将字节流转为一系列结构化对象的过程。

 

    序列化用在两个地方:进程间通信和持久存储。

 

    在Hadoop中,节点之间的进程间通信是用远程过程调用(RPC)。RPC协议将使用序列化将消息编码为二进制流(发送到远程节点),此后在接收端二进制流被反序列化为消息。

 

    Hadoop使用自己的序列化格式Writables。

 

3.1 Writable接口

   

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; //从二进制格式的流读出其状态
}

 

WritableComparable和Comparator

    IntWritable实现了WritableComparable接口。而WritableComparable继承了Writable和Comparable。

   

    类型的比较对MapReduce而言至关重要,键和键之间的比较是在排序阶段完成的。Hadoop提供饿了优化方法:

   

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);
}

该接口允许执行者比较从流中读取的未被反序列化为对象的记录,省去了创建对象的所有开销。例如,IntWritable使用原始的compare()方法从每个字节数组的指定开始位置(S1和S2)和长度(L1和L2)读取整数直接比较。

 

    WritableComparator是RawComparator对WritableComparable类的一个通用实现。第一,它提供一个默认的对原始compare()函数调用,对要比较的对象进行反序列化,然后调用对象的compare()方法。 第二,充当RawComparator实例的一个工厂方法。

    RawComparator<IntWritable> comparator = WritableComparator.get(IntWritable.class)

 

3.2 Writable类

   

未命名

 

 

 

3.2.1 Writable的java基本类封装

    Java基本类型                          Writable使用                      序列化大小(字节)

   布尔类型                                BooleanWritable                1

   字节型                                   ByteWritable                    1

   整型                                     IntWritable                       4

                                             VIntWritable                     1-5

   浮点行                                   FloatWritable                    4

   长整型                                   LongWritable                    8

                                             VLongWritable                   1-9

   双精度浮点型                           DoubleWritable                  8

 

 

4 基于文件的数据结构

 

4.1 SequenceFile类

    SequenceFile为二进制键值对对提供一个持久化的数据结构。

 

4.1.1 写SequenceFile类

   创建SequenceFile类:SequenceFile.createWriter(....)

  

package com.laos.hadoop;

import java.io.IOException;
import java.net.URI;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IOUtils;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.SequenceFile;
import org.apache.hadoop.io.Text;

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 = args[0];
        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()); //参数:文件系统,configuration,路径,键的类型和值的类型
            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);
        }
    }
}

 

4.1.2 读取SequenceFile类

 

 

0 1 package com.laos.hadoop;
0 2
0 3 import java.io.IOException;
0 4 import java.net.URI;
0 5
0 6 import org.apache.hadoop.conf.Configuration;
0 7 import org.apache.hadoop.fs.FileSystem;
0 8 import org.apache.hadoop.fs.Path;
0 9 import org.apache.hadoop.io.IOUtils;
10 import org.apache.hadoop.io.SequenceFile;
11 import org.apache.hadoop.io.Writable;
12 import org.apache.hadoop.util.ReflectionUtils;
13
14 public class SequenceFileReadDemo {
15     public static void main(String[] args) throws IOException {
16         String uri = args[0];
17         Configuration conf = new Configuration();
18         FileSystem fs = FileSystem.get(URI.create(uri), conf);
19         Path path = new Path(uri);
20         SequenceFile.Reader reader = null;
21         try {
22             reader = new SequenceFile.Reader(fs, path, conf);//创建reader
23             Writable key = (Writable) ReflectionUtils.newInstance(reader
24                     .getKeyClass(), conf); //获取key的类型
25             Writable value = (Writable) ReflectionUtils.newInstance(reader
26                     .getValueClass(), conf);//获取value的类型
27             long position = reader.getPosition();//获取位置
28             while (reader.next(key, value)) {//遍历
29                 String syncSeen = reader.syncSeen() ? "*" : "";
30                 System.out.printf("[%s%s]/t%s/t%s/n", position, syncSeen, key,
31                         value);
32                 position = reader.getPosition(); // beginning of next record
33             }
34         } finally {
35             IOUtils.closeStream(reader);
36         }
37     }
38 }

   

    两种方法查找文件中指定的位置,第一种是seak()方法。如果文件中指定位置不是记录边界,reader会在调用next方法是失败。

    第二种是SequenceFile.Reader.sync(long pposition)把reader定位到下一个同步点

 

4.1.3 用命令行接口显示序列文件

     使用-text选项显示文本格式的序列文件。

    % hadoop fs -text number.seq

4.1.4 序列文件的格式

    

    SequeceFile是Hadoop API提供的一种二进制文件支持。这种二进制文件直接将<key, value>对序列化到文件中。一般对小文件可以使用这种文件合并,即将文件名作为key,文件内容作为value序列化到大文件中。这种文件格式有以下好处:
    1)支持压缩,且可定制为基于Record或Block压缩(Block级压缩性能较优)
    2)本地化任务支持:因为文件可以被切分,因此MapReduce任务时数据的本地化情况应该是非常好的。
    3)难度低:因为是Hadoop框架提供的API,业务逻辑侧的修改比较简单。
   

    SequenceFile 是一个由二进制序列化过的key/value的字节流组成的文本存储文件,它可以在map/reduce过程中的input/output 的format时被使用。在map/reduce过程中,map处理文件的临时输出就是使用SequenceFile处理过的。
    SequenceFile分别提供了读、写、排序的操作类。
    SequenceFile的操作中有三种处理方式:
    1) 不压缩数据直接存储。 //enum.NONE
    2) 压缩value值不压缩key值存储的存储方式。//enum.RECORD
    3)key/value值都压缩的方式存储。//enum.BLOCK

 

   

未命名1

没有压缩和记录压缩的序列文件的内部结构:未压缩和记录压缩的结构是一样的,record由记录长度、键长度、键和值(或压缩过的值)构成。

 

 

未命名

块压缩的序列文件的内部结构:一个同步点内记录笔数、压缩键的长度、压缩过的键值、压缩过值的长度和压缩值。压缩块一次压缩多个记录。块的最小大小由属性:io.seqfile.compress.blocksize定义。

 

 

 

 

4.2 MapFile

    MapFile是经过排序的带索引的SequenceFile,可以根据键值进行查找。

4.2.1 写MapFile

    

0 1 package com.laos.hadoop;
0 2
0 3 import java.io.IOException;
0 4 import java.net.URI;
0 5
0 6 import org.apache.hadoop.conf.Configuration;
0 7 import org.apache.hadoop.fs.FileSystem;
0 8 import org.apache.hadoop.io.IOUtils;
0 9 import org.apache.hadoop.io.IntWritable;
10 import org.apache.hadoop.io.MapFile;
11 import org.apache.hadoop.io.Text;
12
13 public class MapFileWriteDemo {
14     private static final String[] DATA = { "One, two, buckle my shoe",
15             "Three, four, shut the door", "Five, six, pick up sticks",
16             "Seven, eight, lay them straight", "Nine, ten, a big fat hen" };
17
18     public static void main(String[] args) throws IOException {
19         String uri = args[0];
20         Configuration conf = new Configuration();
21         FileSystem fs = FileSystem.get(URI.create(uri), conf);
22         IntWritable key = new IntWritable();
23         Text value = new Text();
24         MapFile.Writer writer = null;
25         try {
26             writer = new MapFile.Writer(conf, fs, uri, key.getClass(), value
27                     .getClass()); //创建MapFile   Writer的实例
28
29             for (int i = 0; i < 1024; i++) {
30                 key.set(i + 1);
31                 value.set(DATA[i % DATA.length]);
32                 writer.append(key, value);
33             }
34         } finally {
35             IOUtils.closeStream(writer);
36         }
37     }
38 }

    % hadoop MapFileWriteDemo numbers.map

    numbers.map确实是一个目录,包含data和index两个文件。数据文件包括所有的输入,index文件包含一小部分键和键到data文件中偏移量的映射。索引中键的个数由io.map.index.interval属性设置。

 

4.2.2 读MapFile

    顺序遍历MapFile过程和读取SequenceFile过程相似:创建一个MapFile Reader,调用next函数直到返回false。

    public boolean next(WritableComparable key, Writable val) throws IOException

   

    随机访问:

    public Writable get(WritableComparable key, Writable val) throws IOException

 

4.2.3 将SequenceFile转换成MapFile

 

     关键是给SequenceFile重建索引:使用MapFile的静态方法fix()。

   

0 1 package com.laos.hadoop;
0 2
0 3 import java.net.URI;
0 4
0 5 import org.apache.hadoop.conf.Configuration;
0 6 import org.apache.hadoop.fs.FileSystem;
0 7 import org.apache.hadoop.fs.Path;
0 8 import org.apache.hadoop.io.MapFile;
0 9 import org.apache.hadoop.io.SequenceFile;
10
11 public class MapFileFixer {
12     public static void main(String[] args) throws Exception {
13      String mapUri = args[0];
14     
15      Configuration conf = new Configuration();
16     
17      FileSystem fs = FileSystem.get(URI.create(mapUri), conf);
18      Path map = new Path(mapUri);
19      Path mapData = new Path(map, MapFile.DATA_FILE_NAME);
20     
21      // Get key and value types from data sequence file
22      SequenceFile.Reader reader = new SequenceFile.Reader(fs, mapData, conf);
23      Class keyClass = reader.getKeyClass();
24      Class valueClass = reader.getValueClass();
25      reader.close();
26     
27      // Create the map file index file
28      long entries = MapFile.fix(fs, map, keyClass, valueClass, false, conf);
29      System.out.printf("Created MapFile %s with %d entries/n", map, entries);
30      }
31 }

 

   

   

   

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值