我有一个小程序,每秒在HDFS上将10条记录写入块压缩的SequenceFile,然后每5分钟运行一次sync(),以确保超过5分钟的所有记录都可用于处理。
由于我的代码很多,我只提取了重要的部分:
// initialize
Configuration hdfsConfig = new Configuration();
CompressionCodecFactory codecFactory = new CompressionCodecFactory(hdfsConfig);
CompressionCodec compressionCodec = codecFactory.getCodecByName("default");
SequenceFile.Writer writer = SequenceFile.createWriter(
hdfsConfig,
SequenceFile.Writer.file(path),
SequenceFile.Writer.keyClass(LongWritable.class),
SequenceFile.Writer.valueClass(Text.class),
SequenceFile.Writer.compression(SequenceFile.CompressionType.BLOCK;, compressionCodec)
);
// ...
// append
LongWritable key = new LongWritable((new Date).getTime());
Text val = new Text("Some value");
writer.append(key, val);
// ...
// then every 5 minutes...
logger.info("about to sync...");
writer.hsync();
logger.info("synced!");
仅从日志中,同步操作似乎按预期工作,但HDFS上的文件仍然很小。过了一会儿,可能会添加一些标题和一些事件,但甚至接近频率,因为我hsync()。文件关闭后,一切都会立即刷新。
在每个预期的同步之后还尝试手动检查文件的内容以查看数据是否存在,但是,此处的文件也显示为空: hdfs dfs -text filename
是否有任何已知的原因,为什么writer.hsync()不起作用,如果有的话,是否有任何解决方法?
针对此问题的进一步测试案例:
import java.util.HashMap;
import java.util.Map;
import java.util.Date;
import java.util.Calendar;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.fs.FSDataOutputStream;
import org.apache.hadoop.io.SequenceFile;
import org.apache.hadoop.io.compress.CompressionCodecFactory;
import org.apache.hadoop.io.compress.CompressionCodec;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.io.Writable;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.text.DateFormat;
import java.text.ParseException;
import java.util.Calendar;
import java.util.Date;
import java.util.Locale;
public class WriteTest {
private static final Logger LOG = LoggerFactory.getLogger(WriteTest.class);
public static void main(String[] args) throws Exception {
SequenceFile.CompressionType compressionType = SequenceFile.CompressionType.RECORD;
CompressionCodec compressionCodec;
String compressionCodecStr = "default";
CompressionCodecFactory codecFactory;
Configuration hdfsConfig = new Configuration();
codecFactory = new CompressionCodecFactory(hdfsConfig);
compressionCodec = codecFactory.getCodecByName(compressionCodecStr);
String hdfsURL = "hdfs://10.0.0.1/writetest/";
Date date = new Date();
Path path = new Path(
hdfsURL,
"testfile" + date.getTime()
);
SequenceFile.Writer writer = SequenceFile.createWriter(
hdfsConfig,
SequenceFile.Writer.keyClass(LongWritable.class),
SequenceFile.Writer.valueClass(Text.class),
SequenceFile.Writer.compression(compressionType, compressionCodec),
SequenceFile.Writer.file(path)
);
for(int i=0;i<10000000;i++) {
Text value = new Text("New value!");
LongWritable key = new LongWritable(date.getTime());
writer.append(key, value);
writer.hsync();
Thread.sleep(1000);
}
writer.close();
}
}
结果是在开始编写sequencefile头时有一个fsync,然后没有更多的fsyncs。文件关闭后,内容将写入光盘。
答案 0 :(得分:0)
这里有很多问题。
当您对序列文件使用块压缩时,这意味着许多条目将在内存中缓冲,然后在达到限制或调用sync
时以块压缩形式写入手动
当您在作者上致电hsync
时,它会在其基础hsync
上调用FSDataOutputStream
。但是,这不会将位于压缩缓冲区中的数据写入内存中。因此,为了可靠地将数据传输到Datanode,您必须先调用sync
然后再调用hsync
。
请注意,这样做意味着发送到Datanode的块压缩部分包含的条目少于通常的条目。这会对压缩质量产生负面影响,并可能导致更多的光盘使用。 (我想这就是为什么hsync
没有在内部调用sync
的原因。)
调用fsync
将数据发送到Datanode,但不会将新文件大小报告给namenode。可以找到here和here的技术讨论。显然,每次更新长度都会对性能造成不利影响。有hsync
的特殊版本允许更新Namenode信息,但SequenceFile.Writer
不会公开它。
* @param syncFlags
* Indicate the semantic of the sync. Currently used to specify
* whether or not to update the block length in NameNode.
*/
public void hsync(EnumSet<SyncFlag> syncFlags) throws IOException {
flushOrSync(true, syncFlags);
}
一方面,大小问题意味着即使某些工具报告的文件大小不变,但数据仍然可以安全地到达Datanodes,并且可以在打开InputStream时读取。另一方面,SequenceFile.Reader中存在压缩类型Record
和None
的错误。使用这些压缩类型,Reader使用长度信息来确定读取的距离。由于hsync
未更新此长度信息,即使数据实际可用,它也会错误地停止读取。 Block
压缩读取显然不使用长度信息,并且不会受到此错误的影响。