- Hadoop用户肯定都希望系统在存储和处理数据时,数据不会有任何丢失或损坏。但是,尽管磁盘或网络上的每个I/O操作不太可能将错误引入自己正在读写的数据,但是,如果系统需要处理的数据量大到Hadoop能够处理的极限,数据被损坏的概率还是很高的。
- 检测数据是否损坏的常见措施是,在数据第一次引入系统时计算校验和(checksum),并在数据通过一个不可靠的通道进行传输时再次计算校验和,这样就能发现数据是否损坏。如果计算所得的新校验和和原来的校验和不匹配,就认为数据已损坏。但该技术并不能修复数据——它只能检测出数据错误。(这正是不使用低端硬件的原因。具体说来,一定要使用ECC内存。)注意,校验和也是可能损坏的,不只是数据,但由于校验和比数据小得多,所以损坏的可能性非常小。
- 常用的错误检测码是CRC-32(循环冗余校验),任何大小的数据输入均计算得到一个32位的整数校验和。
- 针对由每个 io.bytes.per.checksum指定字节的数据计算校验和。默认情况下为512个字节,由于CRC-32校验和是4个字节,所以存储校验和的额外开销低于1%
- 它在收到客户端的数据或复制期间其他datanode的数据时执行这个操作
- 正在写数据的客户端将数据及其校验和发送到由一系列datanode组成的管线,管线中最 后一个datanode负责验证校验和。
- 如果datanode监测到错误,客户端便会收到一个CheckSumException异常。
- 每个datanode均持久保存有一个用于验证的校验和日志,所以它知道每个数据块的最后一次验证时间
- 客户端成功验证一个数据块后,会告诉这个datanode,datanode由此更新日志
- 定期验证存储在这个datanode上的所有数据块
- 客户端在读取数据块时,如果监测到错误,就向namenode报告已损坏的数据块及其正在尝试操作的这个datanode,最后才抛出CheckSumException异常。
- Namenode将这个已损坏的数据块的副本标记为已损坏,之后它安排这个数据块的一个副本复制到另一个datanode,这样数据块的副本因子又回到期望水平
- 已损坏的数据块副本便被删除
- fs.setVerifyChecksum(false) fs.open(new Path("")) // 就不进行校验检查了
- Hadoop fs –get –ignoreCrc hdfs://master:9000/a.txt
- Hadoop fs –copyToLocal hdfs://master:9000/a.txt
%gzip -1 file
不同的压缩工具具有不同的压缩特性。
codec 实现了一种压缩·解压缩算法。在Hadoop中,一个对CompressionCodec接口的实现代表一个codec。例如,GzipCodec包装了gzip的压缩和解压缩算法。 Hadoop实现的codec如下
CompressionCodec包含两个函数,用于压缩和解压缩数据
对写入输出流的数据进行压缩,用该方法在底层的数据流中对需要以压缩格式写入一个CompressionOutput在此之前尚未压缩的数据新建Stream对象。
对输入数据流中读取的数据进行解压缩的时候,则调用获取CompressionInputStream,可通过该方法从底层数据流读取解压缩后的数据。
public class GzipCompress {
public static void main(String[] args) throws ClassNotFoundException, IOException {
String codecClassName = "org.apache.hadoop.io.compress.GzipCodec";
String localUrl = "/home/username/sogou500w";
String hdfsUrl = "hdfs://master1:9000/sogou500w.gz";
Class<?> codecClass = Class.forName(codecClassName);
Configuration conf = new Configuration();
CompressionCodec codec = (CompressionCodec) ReflectionUtils.newInstance(codecClass, conf);
FileSystem fs = FileSystem.get(URI.create(hdfsUrl), conf);
InputStream in = new BufferedInputStream(new FileInputStream(localUrl));
OutputStream out = fs.create(new Path(hdfsUrl));
CompressionOutputStream compressOut = codec.createOutputStream(out);
System.out.println(compressOut);
IOUtils.copyBytes(in, compressOut, 1024,true);
}
}
public class GzipDecompress {
public static void main(String[] args) throws Exception{
String uri = args[0];
Configuration conf = new Configuration();
FileSystem fs = FileSystem.get(URI.create(uri), conf);
Path path = new Path(uri);
CompressionCodecFactory factory = new CompressionCodecFactory(conf);
CompressionCodec codec = factory.getCodec(path);
if(codec == null){
System.out.println("No codec found for " + uri);
System.exit(1);
}
String outputUri = CompressionCodecFactory.removeSuffix(uri, codec.getDefaultExtension());
InputStream in = null;
OutputStream out = null;
try{
in = codec.createInputStream(fs.open(path));
out = fs.create(new Path(outputUri));
IOUtils.copyBytes(in, out, conf);
} finally{
IOUtils.closeStream(in);
IOUtils.closeStream(out);
}
}
}
如果输入文件是压缩的,那么在根据文件扩展名推断出相应的codec后,MapReduce会在读取文件时自动解压缩文件
通过CompressionCodecFactory推断CompressionCodec
对MapReduce作业的输出进行压缩操作,应在作业配置过程中,将mapred.output.compress属性设为true和mapred.output.compression.codec属性设置为打算使用的压缩codec的类名
conf.setBoolean(“mapred.output.compress”, true);
conf.setClass (“mapred.output.compression.codec”, GzipCodec.class, CompressionCodec.class);
如以下代码:
public class WordCount {
public static void main(String[] args)throws Exception {
Configuration conf = new Configuration();
Job job = new Job(conf, "WordCount");
job.setJarByClass(WordCount.class);
job.setMapperClass(WordMap.class);
job.setReducerClass(WordReduce.class);
FileInputFormat.setInputPaths(job, new Path(args[0]));
FileOutputFormat.setOutputPath(job, new Path(args[1]));
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(IntWritable.class);
//设置MapReduce作业的输出的压缩格式
conf.setBoolean("mapred.output.compress", true);
conf.setClass ("mapred.output.compression.codec", GzipCodec.class, CompressionCodec.class);
job.waitForCompletion(true);
}
}
如果MapReduce作业输出生成顺序文件(sequence file),可以设置mapred.output.compression.type属性来控制要使用哪种压缩格式默认值是RECORD,即针对每条纪录进行压缩。如果将其改为BLOCK,将针对一组纪录进行压缩,这是推荐的压缩策略,因为它的压缩效率更高
SequenceFileOutputFormat.setOutputCompressionType(job, SequenceFile.CompressionType.RECORD); SequenceFileOutputFormat.setOutputCompressionType(job, SequenceFile.CompressionType.BLOCK);
对map任务输出进行压缩
尽管MapReduce被用读写的是未经压缩的数据,但如果对map阶段的中间输入进行压缩,也可以获得不少好处。由于map任务的输出需要写到磁盘并通过网络传输到reducer节点,所以如果使用LZO这样的快速压缩方式,是可以获得性能提升的,因为需要传输的数据减少了。
下面是在作业中启用map任务输出gzip压缩格式的代码:
conf.setBoolean(job.MAP_OUTPUT_COMPRESS, true);
conf.setClass(job.MAP_OUTPUT_COMPRESS_CODEC, GzipCodec.class, CompressionCodec.class);
所谓序列化(Serialization),是指将结构化对象转化为字节流,以便在网络上传输或写到磁盘进行永久存储
反序列化(deserialization)是指将字节流转回结构化对象的逆过程
序列化在分布式数据处理的两大领域经常出现
进程间通信
永久存储
Hadoop中,系统中多个节点上进程间的通信是通过“远程过程调用”(Remote Procedure Call)RPC实现的。
RPC协议将消息序列化成二进制流后发送到远程节点,远程节点接着将二进制流反序列化为原始消息。
RPC序列化格式
数据永久存储所期望的4个RPC序列化属性非常重要
紧凑,进而高效实用存储空间
快速,进而读写数据的额外开销比较小
可扩展,进而可以透明的读取老格式的数据
互操作,进而可以使用不同的语言读写永久存储的数据
public interface Writable {
void write(DataOutput out) throws IOException;
void readFields(Datalnput in) throws IOException;
}
IntWritable writable=new IntWritable();
writable.set (163);
writable.get()
IntWritable writable=new IntWritable(163);
Writable接口-IntWritable
public class WritableTest {
public static void main(String[] args)throws Exception {
//序列化的过程
IntWritable one = new IntWritable(199);
FileOutputStream out = new FileOutputStream("/home/zkpk/a.txt");
DataOutput out2 = new DataOutputStream(out);
one.write(out2);
//反序列化的过程
IntWritable two = new IntWritable();
FileInputStream in = new FileInputStream("/home/zkpk/a.txt");
DataInput input = new DataInputStream(in);
two.readFields(input);
System.out.println(two.get());
}
}
public class serializable {
public static void main(String[] args) throws IOException {
IntWritable writable = new IntWritable(111);
System.out.println("");
byte[] bytes = serialize(writable);
System.out.println("serialize length: "+bytes.length); //4,一个整数占四个字节
Writable intWritable = deserialize(writable, bytes);
System.out.println(writable.get()); //111
System.out.println(intWritable); //111
}
/** * 序列化 */
public static byte[] serialize(Writable writable) throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
DataOutputStream dataOutput = new DataOutputStream(out);
writable.write(dataOutput);
dataOutput.close();
return out.toByteArray();
}
/** * 反序列化 */
public static Writable deserialize(Writable writable ,byte[] bytes) throws IOException{
ByteArrayInputStream in = new ByteArrayInputStream(bytes);
DataInputStream inputStream = new DataInputStream(in);
writable.readFields(inputStream);
in.close();
inputStream.close();
return writable;
}
}
public interface WritableComparable extends Writable,Comparable { }
public interface RawComparator<T> extends Comparator<T> {
public int compare(byte[] bl, int s1, int l1, byte[] b2. int s2, int l2);
}
根据IntWritable接口实现的comparator实现了compare()方法,该方法可以从每个字节数组b1和b2中读取给定起始位置(s1和s2)以及长度(11和12)的一个整数进而直接进行比较。
第一,它提供了对原始compare()方法的一个默认实现,该方法能够反序列化将在流中进行比较的对象,并调用对象的compare()方法。
第二,它充当的是RawComparator实例的工厂
RawComparator comparator=WritableComparator.get(IntWritable. class); //工厂
这个comparator可以用于比较两个IntWritable对象比较:
IntWritable wl=new IntWritable(163);
IntWritable w2=new IntWritable(67);
System.out.println(comparator.compare(wl, w2))
序列化后直接比较:
byte[] bl = serialize(wl);
byte[] b2 = serialize(w2);
int result2 = comparator.compare(b1, 0, b1.length, b2, 0, b2.length);
System.out.println(result2);
public class RawComparatorTest {
public static void main(String[] args) throws IOException {
RawComparator<IntWritable> comparator = WritableComparator.get(IntWritable.class);
IntWritable i1 = new IntWritable(31);
IntWritable i2 = new IntWritable(3);
int result = comparator.compare(i1, i2);
System.out.println(result);
byte[] b1 = serialize(i1);
byte[] b2 = serialize(i2);
int result2 = comparator.compare(b1, 0, b1.length, b2, 0, b2.length);
System.out.println(result2);
}
public static byte[] serialize(Writable writable) throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
DataOutput output = new DataOutputStream(out);
writable.write(output);
return out.toByteArray();
}
}
public class TestText {
public static void main(String[] args) {
Text text=new Text("hadoop");
System.out.println(text);
System.out.println(text.charAt(2)+"\t"+(int)'d');
String t =new String("hadoop");
System.out.println(t);
System.out.println ("t.length(): "+t.length());
System.out.println("t.getBytes().length: "+t.getBytes().length);
System.out.println(t.charAt(2)+"\t"+(int)'d');
System.out.println("------------");
Text txt=new Text("object");
System.out.println(txt);
String txtStr =txt.toString();
byte[] bt=txtStr.getBytes();
for(byte b: bt){
System.out.print(b+"\t");
}
}
}
NullWritable是Writable的一个特殊类型
public class SequenceFileWriteDemo {
public static final String[] DATA = {
"One,two,buckle my shoe",
"Three, four,shut the door",
"Five,six,pick up sticks",
"Seven,eight,lay them stright",
"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());
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 SequenceFileReaderDemo {
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);
SequenceFile.Reader reader = null;
try{
reader = new SequenceFile.Reader(fs, path, conf);
IntWritable key = (IntWritable) ReflectionUtils.newInstance(reader.getKeyClass(), conf);
Text value = (Text) 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();
}
} finally {
IOUtils.closeStream(reader);
}
}
}