今年7、8月份杭州实行拉闸限电时,导致阿里余杭机房的机器意外断电,造成HDFS集群上的部分数据 丢失。
在Hadoop 2.0.2-alpha之前,HDFS在机器断电或意外崩溃的情况下,有可能出现正在写的数据丢失的 问题。而最近刚发布的CDH4中HDFS在Client端提供了hsync()的方法调用(HDFS-744),从而保证在机器崩 溃或意外断电的情况下,数据不会丢失。这篇文件将围绕这个新的接口对其实现细节进行简单的分析,从 而希望找出一种合理使用hsync()的策略,避免重要数据丢失。
HDFS中sync(),hflush()和hsync()的差别
在hsync()之前,HDFS就已经提供了sync()和hflush()的调用,单从方法的名称上看,很难分辨这三个 方法之间的区别。咱们先从这几个方法之间的差别介绍起。
在HDFS中,调用hflush()会将Client端buffer中的存放数据更新到Datanode端,直到收到所有 Datanode的ack响应时结束调用。这样可保证在hflush()调用结束时,所有的Client端都可以读到一致的 数据。HDFS中的sync()本质也是调用hflush()。
hsync()则是除了确保会将Client端buffer中的存放数据更新到Datanode端外,还会确保Datanode端的 数据更新到物理磁盘上,这样在hsync()调用结束后,即使Datanode所在的机器意外断电,数据并不会因 此丢失。而hflush()在机器意外断电的情况下却有可能丢失数据,因为Client端传给Datanode的数据可能 存在于Datanode的cache中,并未持久化到磁盘上。下图描述了从Client发起一次写请求后,在HDFS中的 数据包传递的流程。
hsync()的实现本质
hsync()执行时,实际上会在对应Datanode的机器上产生一个fsync的系统调用,从而将内存中的相关 文件的数据更新到磁盘。
Client端执行hsync时,Datanode端会识别到Client发送过来的数据包中的syncBlock_字段为true,从 而判定需要将内存中的数据更新到磁盘。此时会在BlockReceiver.java的flushOrSync()中执行如下语句 :
((FileOutputStream)cout).getChannel().force(true);
而FileChannel的force(boolean metadata)方法在JDK中,底层为于FileDispatcherImpl.c中调用 fsync或fdatasync。metadata为true时执行fsync,为false时执行fdatasync。
Java_sun_nio_ch_FileDispatcherImpl_force0(JNIEnv *env, jobject this, jobject fdo, jboolean md) { jint fd = fdval(env, fdo); int result = 0; if (md == JNI_FALSE) { result = fdatasync(fd); } else { result = fsync(fd); } return handle(env, result, "Force failed"); }