客户端在连接hiveserver2时,会在hdfs上创建${hive.exec.scratchdir}/<username> (开启doAs为登录用户,否则为启动用户)目录,用于存放作业执行过程中产生的临时文件,在执行某些作业时会产生大量的临时文件,如遇客户端异常或jvm异常退出,造成数据无法清理。
hive提供如下方案解决清理临时文件问题:
1、 hive.start.cleanup.scratchdir : true 默认 :false
hiveserver2在启动的时候回清理 ${hive.exec.scratchdir}/<username> 下目录,若为多hiveserver2 服务且目录配置一致时,会造成删除正在执行作业的临时目录
2、hive.scratchdir.lock :true 默认: false
在scratchdir中保存锁文件,以防止被cleardanglingscratchdir删除
3、hive.server2.clear.dangling.scratchdir :true 默认 :false
开启一个线程池自动清理异常情况未被清理的临时目录,和hive.scratchdir.loc共同管理临时目录的清理,如果客户端处于活动状态,则锁定文件保持打开状态,并且 cleardanglingscratchdir 将检测到它并且不将其视为悬空暂存目录。只有当 Hive 进程意外死亡并留下临时目录时,它才会成为 cleardanglingscratchdir 删除的目标。
如上所述,有如下方案处理临时目录:
1、hive.start.cleanup.scratchdir 设置true;在hive启动时自动清理临时目录,对于多hiveserver2服务,修改hive.exec.scratchdir 配置,以服务命名。
2、hive.scratchdir.lock 设置true,hive.server2.clear.dangling.scratchdir设置true;在hiveserver2服务端口开启临时目录清理线程池,自动清理未被占用的临时目录。
建议采用方案1,只是在启动时清理临时目录,hiveserver2 也有自己的session管理机制,超时异常的session会被清理,临时目录也会被清理,所以异常生成的临时目录不会很多,方案2虽然会自动处理,但会在每个临时目录下生产锁文件,对于服务任务负载高时也会对hdfs产生影响,并且需要清理的临时目录也不会很多,故建议方案1;hiveserver2的正确下线方式为先删除注册到zookeeper上的临时目录,这样客户端就不会通过连接串登录此服务, 当该服务上的作业运行完成session退出时,hiveserver2服务也会停止。
如下为hiveserver2自动清理临时目录的逻辑,利用hdfs的文件锁判断是否被占用,借鉴
public void run() {
try {
//获取session临时目录即 hive.exec.scratchdir目录
Path rootHDFSDirPath = new Path(rootHDFSDir);
FileSystem fs = FileSystem.get(rootHDFSDirPath.toUri(), conf);
FileStatus[] userHDFSDirList = fs.listStatus(rootHDFSDirPath);
List<Path> scratchDirToRemove = new ArrayList<Path>();
for (FileStatus userHDFSDir : userHDFSDirList) {
FileStatus[] scratchDirList = fs.listStatus(userHDFSDir.getPath());
for (FileStatus scratchDir : scratchDirList) {
Path lockFilePath = new Path(scratchDir.getPath(), SessionState.LOCK_FILE_NAME);
//判断是否有锁文件,没有掉过,不做清理
if (!fs.exists(lockFilePath)) {
String message = "Skipping " + scratchDir.getPath() + " since it does not contain " +
SessionState.LOCK_FILE_NAME;
if (verbose) {
consoleMessage(message);
}
continue;
}
boolean removable = false;
boolean inuse = false;
try {
//往此文件追加内容
IOUtils.closeStream(fs.append(lockFilePath));
removable = true;
} catch (RemoteException eAppend) {
// RemoteException with AlreadyBeingCreatedException will be thrown
// if the file is currently held by a writer
//如果文件当前由编写器持有,将引发带有AlreadyBeingCreateDexException的RemoteException
if(AlreadyBeingCreatedException.class.getName().equals(eAppend.getClassName())){
inuse = true;
} else if (UnsupportedOperationException.class.getName().equals(eAppend.getClassName())) {
// Append is not supported in the cluster, try to use create
//群集中不支持追加,请尝试使用创建
try {
IOUtils.closeStream(fs.create(lockFilePath, false));
} catch (RemoteException eCreate) {
if (AlreadyBeingCreatedException.class.getName().equals(eCreate.getClassName())){
// If the file is held by a writer, will throw AlreadyBeingCreatedException
//如果文件由编写器保存,将抛出AlreadyBeingCreatedException
inuse = true;
} else {
consoleMessage("Unexpected error:" + eCreate.getMessage());
}
} catch (FileAlreadyExistsException eCreateNormal) {
// Otherwise, throw FileAlreadyExistsException, which means the file owner is
// dead
//否则,抛出FileAlreadyExistsException,这意味着文件所有者已死亡,后续可以清理此临时目录
removable = true;
}
} else {
consoleMessage("Unexpected error:" + eAppend.getMessage());
}
}
if (inuse) {
// Cannot open the lock file for writing, must be held by a live process
String message = scratchDir.getPath() + " is being used by live process";
if (verbose) {
consoleMessage(message);
}
}
if (removable) {
scratchDirToRemove.add(scratchDir.getPath());
}
}
}
if (scratchDirToRemove.size()==0) {
consoleMessage("Cannot find any scratch directory to clear");
return;
}
consoleMessage("Removing " + scratchDirToRemove.size() + " scratch directories");
for (Path scratchDir : scratchDirToRemove) {
if (dryRun) {
System.out.println(scratchDir);
} else {
boolean succ = fs.delete(scratchDir, true);
if (!succ) {
consoleMessage("Cannot remove " + scratchDir);
} else {
String message = scratchDir + " removed";
if (verbose) {
consoleMessage(message);
}
}
}
}
} catch (IOException e) {
consoleMessage("Unexpected exception " + e.getMessage());
}
}