之前有文章介绍过HDFS租约带来的问题,导致spark应用无法正常读取文件,只能将异常文件找出并且删除后,任务才能继续执行。

但是删除文件实在是下下策,而且文件本身其实并未损坏,只是因为已经close的客户端没有及时的释放租约导致。

按照Hadoop官网的说法,HDFS会启动一个单独的线程,专门处理未及时释放的租约,自动释放超过“硬超时”(默认1小时)仍未释放的租约,但是从问题的现象上来看,这个线程并没有正常的工作,甚至怀疑这个线程是否没有启动,我使用的是CDH集群,可能与相关的设置有关,这一点需要确认。

如果Hadoop没有自动清理租约,我们有办法手动的刷新租约吗?答案是肯定的。

在网上查看资料时,发现HDFS源码中的DistributedFileSystem类提供了一个叫做recoverLease的方法,可以主动的刷新租约。但是非常奇怪,既然已经为外界提供了这个接口,为什么不提供shell指令给用户使用呢?为什么只能通过代码的方式调用呢?我使用的是hadoop-2.6.0,也许后期的版本有所更新,这一点也需要求证。

下面看一下这个方法的源码:

/**
* Start the lease recovery of a file
*
* @param f a file
* @return true if the file is already closed
* @throws IOException if an error occurs
*/
public boolean recoverLease(final Path f) throws IOException {
Path absF = fixRelativePart(f);
return new FileSystemLinkResolver<Boolean>() {
@Override
public Boolean doCall(final Path p)
throws IOException, UnresolvedLinkException {
return dfs.recoverLease(getPathName(p));
}
@Override
public Boolean next(final FileSystem fs, final Path p)
throws IOException {
if (fs instanceof DistributedFileSystem) {
DistributedFileSystem myDfs = (DistributedFileSystem)fs;
return myDfs.recoverLease(p);
}
throw new UnsupportedOperationException("Cannot recoverLease through" +
" a symlink to a non-DistributedFileSystem: " + f + " -> " + p);
}
}.resolve(this, absF);
}

有兴趣的朋友可以下载hadoop源码来仔细推敲一下内部的实现原理,这里我们只说如何调用,解决我们的问题:

    public static void recoverLease(String path) throws IOException {
DistributedFileSystem fs = new DistributedFileSystem();
Configuration conf = new Configuration();
fs.initialize(URI.create(path), conf);
fs.recoverLease(new Path(path));
fs.close();
}

这是我编写的一个调用改接口的简单的封装方法,需要注意的是,此处传入的path,必须是包含文件系统以及namenode和端口号的全路径,比如:

hdfs://namenode1:9000/xxx/xxx.log

如果只需要恢复单个文件,调用上述方法即可,但是通常情况下,我们需要对一个目录进行递归的处理,即恢复指定目录下所有租约异常的文件。

这个时候,我们需要先找出指定目录下所有租约异常的文件,形成一个Set或者List,然后再遍历这个容器,对每个文件进行恢复。

寻找文件列表的方法如下:

public static Set<String> getOpenforwriteFileList(String dir) throws IOException {
/*拼接URL地址,发送给namenode监听的dfs.namenode.http-address端口,获取所需数据*/
StringBuilder url = new StringBuilder();
url.append("/fsck?ugi=").append("dev");
url.append("&openforwrite=1"); /*获得namenode的主机名以及dfs.namenode.http-address监听端口,例如:http://hadoopnode1:50070*/
Path dirpath;
URI namenodeAddress;
dirpath = HDFSUtil.getResolvedPath(dir);
namenodeAddress = HDFSUtil.getDFSHttpAddress(dirpath); url.insert(0, namenodeAddress);
try {
url.append("&path=").append(URLEncoder.encode(
Path.getPathWithoutSchemeAndAuthority(new Path(dir)).toString(), "UTF-8"));
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
} Configuration conf = new Configuration();
URLConnectionFactory connectionFactory = URLConnectionFactory.newDefaultURLConnectionFactory(conf);
URL path = null;
try {
path = new URL(url.toString());
} catch (MalformedURLException e) {
e.printStackTrace();
} URLConnection connection;
BufferedReader input = null;
try {
connection = connectionFactory.openConnection(path, UserGroupInformation.isSecurityEnabled());
InputStream stream = connection.getInputStream();
input = new BufferedReader(new InputStreamReader(stream, "UTF-8"));
} catch (IOException | AuthenticationException e) {
e.printStackTrace();
} if (input == null) {
System.err.println("Cannot get response from namenode, url = " + url);
return null;
} String line;
Set<String> resultSet = new HashSet<>();
try {
while ((line = input.readLine()) != null) {
if (line.contains("MISSING") || line.contains("OPENFORWRITE")) {
String regEx = "/[^ ]*";
Pattern pattern = Pattern.compile(regEx);
Matcher matcher = pattern.matcher(line);
while (matcher.find()) {
resultSet.add(matcher.group().replaceAll(":", ""));
}
}
}
} catch (IOException e) {
e.printStackTrace();
} finally {
input.close();
} return resultSet; }

其实获取租约异常列表的方法是我从HDFS源码的org.apache.hadoop.hdfs.tools.DFSck中仿照而来的,通过向NameNode的dfs.namenode.http-address端口通信,获取openforwrite状态的文件列表,然后通过正则匹配以及字符串切割,获取所需的内容。

顺便提一句,由于此代码是Java代码,并且返回的Set类型为java.util.Set,如果在Scala代码中调用,则需要将Set类型转化为scala.collection.immutable.Set,具体方法如下:

    /*获取需要被恢复租约的文件列表,返回类型为java.util.Set*/
val javaFilesSet = HDFSUtil.getOpenforwriteFileList(hdfsPrefix + recoverDirPath)
if (null == javaFilesSet || javaFilesSet.isEmpty) {
println("No files need to recover lease : " + hdfsPrefix + recoverDirPath)
return
} /*将java.util.Set转换成scala.collection.immutable.Set*/
import scala.collection.JavaConverters._
val filesSet = javaFilesSet.asScala.toSet

至此,利用以上两个方法,即可获取指定目录下的所有租约异常的文件列表,然后遍历调用租约恢复接口,即可实现批量恢复。

如何恢复未释放租约的HDFS文件的更多相关文章

  1. [bigdata] 使用Flume hdfs sink, hdfs文件未关闭的问题

    现象: 执行mapreduce任务时失败 通过hadoop fsck -openforwrite命令查看发现有文件没有关闭. [root@com ~]# hadoop fsck -openforwri ...

  2. linux删除文件未释放空间问题处理

    linux删除文件未释放空间问题处理 或者 /根分区满了 (我的根分区是/dev/sda1,/dev/sda1满了) http://blog.csdn.net/donghustone/article/ ...

  3. LINUX文件删除,但磁盘空间未释放

    最近在进行系统压测,由于服务器节点太多,便写了个简单的脚本,在执行过程中发现,日志文件删除后,磁盘空间只释放了一小部分,任有大部分磁盘空间未释放. 使用lsof | grep delete命令,发现已 ...

  4. 【Linux命令】删除大文件后磁盘空间未释放问题

    前言 工作中经常遇到Linux系统磁盘空间不足,但是删除后较大的日志文件后,发现磁盘空间仍没有被释放,有点摸不着头脑,今天博主带大家解决这个问题. 思路 1.工作发现磁盘空间不足: 2.找到占用磁盘空 ...

  5. Linux文件删除空间未释放

    当系统空间使用量过大需要清理空间或者清理某个文件时,有时会出现执行了删除命令之后磁盘空间并没有释放,很多人首次遇到该情况时会比较困惑,在考虑是不是像windows系统的回收站一样,删除只是逻辑删除到回 ...

  6. 如何有效恢复误删的HDFS文件

    HDFS是大数据领域比较知名的分布式存储系统,作为大数据相关从业人员,每天处理HDFS上的文件数据是常规操作.这就容易带来一个问题,实际操作中对重要数据文件的误删,那么如何恢复这些文件,就显得尤为重要 ...

  7. HDFS 文件读写过程

    HDFS 文件读写过程 HDFS 文件读取剖析 客户端通过调用FileSystem对象的open()来读取希望打开的文件.对于HDFS来说,这个对象是分布式文件系统的一个实例. Distributed ...

  8. mysql优化, 删除数据后物理空间未释放(转载)

    mysql优化, 删除数据后物理空间未释放(转载) OPTIMIZE TABLE 当您的库中删除了大量的数据后,您可能会发现数据文件尺寸并没有减小.这是因为删除操作后在数据文件中留下碎片所致.OPTI ...

  9. Delphi窗体创建释放过程及单元文件小结(转)

    Delphi窗体创建释放过程及单元文件小结 Delphi中的窗体,有模式窗体与非模式窗体两种.两种窗体的调用方式不同,模式窗体使用ShowModal显示,非模式窗体使用Show显示.当显示模式窗体的时 ...

随机推荐

  1. java中方法总结(每周更新)

    1.URLEncoder.encode(username,"utf-8")将"utf-8"编码的username先解码,然后再采用URL编码 2.URLDeco ...

  2. hibernate,createCriteria in条件 是一个集合。list 或 数组等

    hibernate,createCriteria in条件 是一个集合.list 或 数组等 cq.in("states", new String[]{"2", ...

  3. RabbitMQ系列教程之四:路由(Routing)

    (使用Net客户端)在上一个教程中,我们构建了一个简单的日志系统,我们能够向许多消息接受者广播发送日志消息.在本教程中,我们将为其添加一项功能 ,这个功能是我们将只订阅消息的一个子集成为可能. 例如, ...

  4. php下redis的安装教程

    1.得有php的环境,推荐使用appserv,下载链接 链接: http://pan.baidu.com/s/1cHqSOY 密码: b8w2 2.安装redis 下载链接 链接: http://pa ...

  5. 拥抱.NET Core系列:依赖注入(1)

    依赖注入时编程手段中解耦和封装的一个非常重要的手段,我本人已经到了没有DI无法编写项目的程度了,在.NET Framework中微软并没有在FCL中引入DI,虽然推出了"Unity" ...

  6. js验证表单密码、用户名是否输入--JS的简单应用

    在登录.注册时,我们经常会遇到下面这种情况,如果我们没有输入用户名.密码时,系统会弹出提示框.提示框信息提示内容是我们密码没有输入密码或者用户名等.那么这样的弹出框效果是如何实现的呢?文章标题既然与j ...

  7. js实现日期格式化

    <!DOCTYPE html><html><head> <meta charset="utf-8"> <meta http-e ...

  8. Centos7架设NMP服务器笔记

    安装centos7.3 1.从mirrors.163.com下载7.3 2.准备虚拟机vitualbox,网络我使用的桥接到无线网卡,直接连我到路由器,IP自动分配(本来想搞静态IP的,搞了好久没成功 ...

  9. python打印万年历

    1.输入年份,输入月份 2.格式化输出本月的日历 3.思路 输入年,月,打印对应年月的日历.1,首先1970年是Unix系统诞生的时间,1970年成为Unix的元年,1970年1月1号是星期四,现在大 ...

  10. Python爬虫从入门到放弃(十七)之 Scrapy框架中Download Middleware用法

    这篇文章中写了常用的下载中间件的用法和例子.Downloader Middleware处理的过程主要在调度器发送requests请求的时候以及网页将response结果返回给spiders的时候,所以 ...