首先声明HDFS并不擅长append操作。
本文以循环追加内容到文件为例,文件大小达到1KB后,重新创建新文件继续写入,写满5个文件后程序停止……,代码如下:
package com.leboop.www;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FSDataOutputStream;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
/**
* Created by leb on 2018/4/16.
*/
public class HDFSAppend {
public static void main(String[] args) throws Exception {
Configuration conf = new Configuration();
// conf.set("fs.defaultFS", "hdfs://192.168.128.11:9000");
FileSystem fs = FileSystem.get(conf);
System.out.println("fs = " + fs);
String content = "Hello Word!\r\n";
Path filePath = new Path("/append/file");
FSDataOutputStream fsDos = null;
int fileCount = 0;
while (true) { // 循环向文件中写入数据
if (!fs.exists(filePath)) {
fsDos = fs.create(filePath, false);
fileCount++;
} else {
if (fs.getFileStatus(filePath).getLen() > 1024) { // 文件大小超过1KB
fs.rename(filePath, new Path("/append/file_" + fileCount));
fsDos = fs.create(filePath, false);
fileCount++;
} else {
fsDos = fs.append(filePath);
}
}
fsDos.writeBytes(content);
if (fileCount > 5) {
break;
}
}
}
}
本地方式
注释掉
conf.set("fs.defaultFS", "hdfs://192.168.128.11:9000");
本地运行结果如下:
fs = org.apache.hadoop.fs.LocalFileSystem@c267ef4
Exception in thread "main" java.io.IOException: Not supported
at org.apache.hadoop.fs.ChecksumFileSystem.append(ChecksumFileSystem.java:357)
at org.apache.hadoop.fs.FileSystem.append(FileSystem.java:1166)
at com.leboop.www.HDFSAppend.main(HDFSAppend.java:32)
Process finished with exit code 1
本地不支持FileSystem的append方法。
集群方式
将程序打包,提交到Hadoop集群测试(也可以把代码注释去掉conf.set("fs.defaultFS", "hdfs://192.168.128.11:9000");),如图:
关键日志如下:
Exception in thread "main" org.apache.hadoop.ipc.RemoteException(org.apache.hadoop.hdfs.protocol.AlreadyBeingCreatedException): Failed to APPEND_FILE /append/file for DFSClient_NONMAPREDUCE_-
136843200_1 on 192.168.128.11 because DFSClient_NONMAPREDUCE_-136843200_1 is already the current lease holder.
集群测试FileSytem支持append方法,
本地方式Not Support和集群AlreadyBeingCreatedException问题解决
丢弃append方法,使用BufferedWriter,如下:
package com.leboop.www;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import java.io.*;
/**
* Created by leb on 2018/4/16.
*/
public class HDFSAppend {
public static void main(String[] args) throws Exception {
Configuration conf = new Configuration();
// conf.set("fs.defaultFS", "hdfs://192.168.128.11:9000");
FileSystem fs = FileSystem.get(conf);
System.out.println("fs = " + fs);
String content = "Hello Word!Hello Word!Hello Word!Hello Word!Hello Word!\r\n";
Path filePath = new Path("/append/file");
int fileCount = 0;
if (fs.exists(filePath)) {
fs.delete(filePath, false);
}
OutputStream os = fs.create(filePath, false);
fileCount++;
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(os));
while (true) { // 循环向文件中写入数据
long size = fs.getFileStatus(filePath).getLen();
if (size > 0) {
System.out.println("size=" + size);
}
if (size > 1024) { // 文件大小超过1KB
bw.flush();
bw.close(); // 重命名前需要关闭流
Path newFile = new Path("/append/file_" + fileCount);
fs.rename(filePath, newFile);
os = fs.create(filePath, false);
bw = new BufferedWriter(new OutputStreamWriter(os));
fileCount++;
}
bw.write(content);
if (fileCount > 5) {
break;
}
}
}
}
程序可以正常执行,如图:
并生成5个文件。但是文件并没有按照1KB分割,与BufferWriter缓存有关。
将代码重新打包提交Hadoop集群测试,如图:
程序运行一段时间后,如图:
HDFS上生产一个和数据块大小相同的文件,如图:
无论是本地还是集群,
fs.getFileStatus(filePath).getLen()
方法都无法获取到文件缓存在内存中的大小,而是达到溢出值写入磁盘后,才可以获取。所以 这种方式并不能按照指定的大小分割文件。
不能按指定文件大小切分解决
(1)方法1
创建BufferWriter时,指定默认buffersize大小为分割大小
(2)方法2
每次调用获取文件大小时,关闭FileSystem,重新创建即可,核心代码如下:
fs.close(); fs=FileSystem.get(conf);
整体代码如下:
package com.leboop.www;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FSDataOutputStream;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
/**
* Created by leb on 2018/4/16.
*/
public class HDFSAppend {
public static void main(String[] args) throws Exception {
Configuration conf = new Configuration();
//conf.set("fs.defaultFS", "hdfs://192.168.128.11:9000");
FileSystem fs = FileSystem.get(conf);
System.out.println("fs = " + fs);
String content = "Hello Word!\r\n";
Path filePath = new Path("/append/file");
FSDataOutputStream fsDos = null;
int fileCount = 0;
while (true) { // 循环向文件中写入数据
fs.close();
fs=FileSystem.get(conf);
if (!fs.exists(filePath)) {
fsDos = fs.create(filePath, false);
fileCount++;
} else {
if (fs.getFileStatus(filePath).getLen() > 1024) { // 文件大小超过1KB
fs.rename(filePath, new Path("/append/file_" + fileCount));
fsDos = fs.create(filePath, false);
fileCount++;
} else {
fsDos = fs.append(filePath);
}
}
fsDos.writeBytes(content);
if (fileCount > 5) {
break;
}
}
}
}
原理是写一次,就刷到磁盘,问题是效率极低。效率问题解决:先写入一个文件,使用IOUtils进行copy切分,或者将整个文件作为mr输入(只包含map操作)按分片切分。