上篇Hadoop之HDFS原理及文件上传下载源码分析(上)楼主主要介绍了hdfs原理及FileSystem的初始化源码解析, Client如何与NameNode建立RPC通信。本篇将继续介绍hdfs文件上传、下载源解析。

文件上传

  先上文件上传的方法调用过程时序图:

  

  

  其主要执行过程:

  1.    FileSystem初始化,Client拿到NameNodeRpcServer代理对象,建立与NameNode的RPC通信(楼主上篇已经介绍过了)
  2.    调用FileSystem的create()方法,由于实现类为DistributedFileSystem,所有是调用该类中的create()方法
  3.    DistributedFileSystem持有DFSClient的引用,继续调用DFSClient中的create()方法
  4.    DFSOutputStream提供的静态newStreamForCreate()方法中调用NameNodeRpcServer服务端的create()方法并创建DFSOutputStream输出流对象返回
  5.    通过hadoop提供的IOUtil工具类将输出流输出到本地

  下面我们来看下源码:

  首先初始化文件系统,建立与服务端的RPC通信

  

  1. HDFSDemo.java
  2. OutputStream os = fs.create(new Path("/test.log"));

  调用FileSystem的create()方法,由于FileSystem是一个抽象类,这里实际上是调用的该类的子类create()方法

  

  1. //FileSystem.java
  2. public abstract FSDataOutputStream create(Path f,
  3. FsPermission permission,
  4. boolean overwrite,
  5. int bufferSize,
  6. short replication,
  7. long blockSize,
  8. Progressable progress) throws IOException;

   前面我们已经说过FileSystem.get()返回的是DistributedFileSystem对象,所以这里我们直接进入DistributedFileSystem:

 

  

  1. //DistributedFileSystem.java
  2. @Override
  3. public FSDataOutputStream create(final Path f, final FsPermission permission,
  4. final EnumSet<CreateFlag> cflags, final int bufferSize,
  5. final short replication, final long blockSize, final Progressable progress,
  6. final ChecksumOpt checksumOpt) throws IOException {
  7. statistics.incrementWriteOps(1);
  8. Path absF = fixRelativePart(f);
  9. return new FileSystemLinkResolver<FSDataOutputStream>() {
  10. @Override
  11. public FSDataOutputStream doCall(final Path p)
  12. throws IOException, UnresolvedLinkException {
  13. final DFSOutputStream dfsos = dfs.create(getPathName(p), permission,
  14. cflags, replication, blockSize, progress, bufferSize,
  15. checksumOpt);
  16. //dfs为DistributedFileSystem所持有的DFSClient对象,这里调用DFSClient中的create()方法
  17. return dfs.createWrappedOutputStream(dfsos, statistics);
  18. }
  19. @Override
  20. public FSDataOutputStream next(final FileSystem fs, final Path p)
  21. throws IOException {
  22. return fs.create(p, permission, cflags, bufferSize,
  23. replication, blockSize, progress, checksumOpt);
  24. }
  25. }.resolve(this, absF);
  26. }

  DFSClient的create()返回一个DFSOutputStream对象:

  

  1. //DFSClient.java
  2. public DFSOutputStream create(String src,
  3. FsPermission permission,
  4. EnumSet<CreateFlag> flag,
  5. boolean createParent,
  6. short replication,
  7. long blockSize,
  8. Progressable progress,
  9. int buffersize,
  10. ChecksumOpt checksumOpt,
  11. InetSocketAddress[] favoredNodes) throws IOException {
  12. checkOpen();
  13. if (permission == null) {
  14. permission = FsPermission.getFileDefault();
  15. }
  16. FsPermission masked = permission.applyUMask(dfsClientConf.uMask);
  17. if(LOG.isDebugEnabled()) {
  18. LOG.debug(src + ": masked=" + masked);
  19. }
  20. //调用DFSOutputStream的静态方法newStreamForCreate,返回输出流
  21. final DFSOutputStream result = DFSOutputStream.newStreamForCreate(this,
  22. src, masked, flag, createParent, replication, blockSize, progress,
  23. buffersize, dfsClientConf.createChecksum(checksumOpt),
  24. getFavoredNodesStr(favoredNodes));
  25. beginFileLease(result.getFileId(), result);
  26. return result;
  27. }

  我们继续看下newStreamForCreate()中的业务逻辑:

  

  1. //DFSOutputStream.java
  2. static DFSOutputStream newStreamForCreate(DFSClient dfsClient, String src,
  3. FsPermission masked, EnumSet<CreateFlag> flag, boolean createParent,
  4. short replication, long blockSize, Progressable progress, int buffersize,
  5. DataChecksum checksum, String[] favoredNodes) throws IOException {
  6. TraceScope scope =
  7. dfsClient.getPathTraceScope("newStreamForCreate", src);
  8. try {
  9. HdfsFileStatus stat = null;
  10. boolean shouldRetry = true;
  11. int retryCount = CREATE_RETRY_COUNT;
  12. while (shouldRetry) {
  13. shouldRetry = false;
  14. try {
  15. //这里通过dfsClient的NameNode代理对象调用NameNodeRpcServer中实现的create()方法
  16. stat = dfsClient.namenode.create(src, masked, dfsClient.clientName,
  17. new EnumSetWritable<CreateFlag>(flag), createParent, replication,
  18. blockSize, SUPPORTED_CRYPTO_VERSIONS);
  19. break;
  20. } catch (RemoteException re) {
  21. IOException e = re.unwrapRemoteException(
  22. AccessControlException.class,
  23. DSQuotaExceededException.class,
  24. FileAlreadyExistsException.class,
  25. FileNotFoundException.class,
  26. ParentNotDirectoryException.class,
  27. NSQuotaExceededException.class,
  28. RetryStartFileException.class,
  29. SafeModeException.class,
  30. UnresolvedPathException.class,
  31. SnapshotAccessControlException.class,
  32. UnknownCryptoProtocolVersionException.class);
  33. if (e instanceof RetryStartFileException) {
  34. if (retryCount > 0) {
  35. shouldRetry = true;
  36. retryCount--;
  37. } else {
  38. throw new IOException("Too many retries because of encryption" +
  39. " zone operations", e);
  40. }
  41. } else {
  42. throw e;
  43. }
  44. }
  45. }
  46. Preconditions.checkNotNull(stat, "HdfsFileStatus should not be null!");
  47. //new输出流对象
  48. final DFSOutputStream out = new DFSOutputStream(dfsClient, src, stat,
  49. flag, progress, checksum, favoredNodes);
  50. out.start();//调用内部类DataStreamer的start()方法,DataStreamer继承Thread,所以说这是一个线程,从NameNode中申请新的block信息;
                    同时前面我们介绍hdfs原理的时候提到的流水线作业(Pipeline)也是在这里实现,有兴趣的同学可以去研究下,这里就不带大家看了
  51. return out;
  52. } finally {
  53. scope.close();
  54. }
  55. }

    

  到此,Client拿到了服务端的输出流对象,那么后面就容易了,都是一些简答的文件输出,输入流的操作(hadoop提供的IOUitl)。

文件下载

  文件上传的大致流程与文件下载类似,与上传一样,我们先上程序方法调用时序图:

  

  主要执行过程:  

  1.    FileSystem初始化,Client拿到NameNodeRpcServer代理对象,建立与NameNode的RPC通信(与前面一样)
  2.    调用FileSystem的open()方法,由于实现类为DistributedFileSystem,所有是调用该类中的open()方法
  3.    DistributedFileSystem持有DFSClient的引用,继续调用DFSClient中的open()方法
  4.    实例化DFSInputStream输入流
  5.    调用openinfo()方法
  6.    调用fetchLocatedBlocksAndGetLastBlockLength()方法,抓取block信息并获取最后block长度
  7. 调用DFSClient中的getLocatedBlocks()方法,获取block信息
  8.    在callGetBlockLocations()方法中通过NameNode代理对象调用NameNodeRpcServer的getBlockLocations()方法
  9. 将block信息写入输出流
  10. 交给IOUtil,下载文件到本地

  接下来,我们开始看源码:

  首先任然是FileSystem的初始化,前面有,这里就不贴出来了,我们直接从DistributedFileSystem的open()开始看。

  

  1. //DistributedFifeSystem.java
  2. @Override
  3. public FSDataInputStream open(Path f, final int bufferSize)
  4. throws IOException {
  5. statistics.incrementReadOps(1);
  6. Path absF = fixRelativePart(f);
  7. return new FileSystemLinkResolver<FSDataInputStream>() {
  8. @Override
  9. public FSDataInputStream doCall(final Path p)
  10. throws IOException, UnresolvedLinkException {
  11. final DFSInputStream dfsis =
  12. dfs.open(getPathName(p), bufferSize, verifyChecksum);
  13. //dfs为DFSClient对象,调用open()返回输入流
  14. return dfs.createWrappedInputStream(dfsis);
  15. }
  16. @Override
  17. public FSDataInputStream next(final FileSystem fs, final Path p)
  18. throws IOException {
  19. return fs.open(p, bufferSize);
  20. }
  21. }.resolve(this, absF);
  22. }

  DFSClient中并没有直接使用NameNode的代理对象,而是传给了DFSInputStream:

  

  1. //DFSClient.java
  2. public DFSInputStream open(String src, int buffersize, boolean verifyChecksum)
  3. throws IOException, UnresolvedLinkException {
  4. checkOpen();
  5. TraceScope scope = getPathTraceScope("newDFSInputStream", src);
  6. try {
  7. //这里并没有直接通过NameNode的代理对象调用服务端的方法,直接new输入流并把当前对象作为参数传入
  8. return new DFSInputStream(this, src, verifyChecksum);
  9. } finally {
  10. scope.close();
  11. }
  12. }

  那么在DFSInputStream必须持有DFSClient的引用:

  

  1. //DFSInputStream.java 构造
  2. DFSInputStream(DFSClient dfsClient, String src, boolean verifyChecksum
  3. ) throws IOException, UnresolvedLinkException {
  4. this.dfsClient = dfsClient;//只有DFSClient的引用
  5. this.verifyChecksum = verifyChecksum;
  6. this.src = src;
  7. synchronized (infoLock) {
  8. this.cachingStrategy = dfsClient.getDefaultReadCachingStrategy();
  9. }
  10. openInfo();//调openInfo()
  11. }

  openInfo()用来抓取block信息:

  1. void openInfo() throws IOException, UnresolvedLinkException {
  2. synchronized(infoLock) {
  3. lastBlockBeingWrittenLength = fetchLocatedBlocksAndGetLastBlockLength();//抓取block信息
  4. int retriesForLastBlockLength = dfsClient.getConf().retryTimesForGetLastBlockLength;//获取配置信息,尝试抓取的次数,楼主记得在2.6以前这里写的3;当然,现在的默认值也为3
  5. while (retriesForLastBlockLength > 0) {
  6. if (lastBlockBeingWrittenLength == -1) {
  7. DFSClient.LOG.warn("Last block locations not available. "
  8. + "Datanodes might not have reported blocks completely."
  9. + " Will retry for " + retriesForLastBlockLength + " times");
  10. waitFor(dfsClient.getConf().retryIntervalForGetLastBlockLength);
  11. lastBlockBeingWrittenLength = fetchLocatedBlocksAndGetLastBlockLength();
  12. } else {
  13. break;
  14. }
  15. retriesForLastBlockLength--;
  16. }
  17. if (retriesForLastBlockLength == 0) {
  18. throw new IOException("Could not obtain the last block locations.");
  19. }
  20. }
  21. }

  获取block信息:

  1. //DFSInputStream.java
  2. private long fetchLocatedBlocksAndGetLastBlockLength() throws IOException {
  3. final LocatedBlocks newInfo = dfsClient.getLocatedBlocks(src, 0);
  4. //回到DFSClient中来获取当前block信息
  5. if (DFSClient.LOG.isDebugEnabled()) {
  6. DFSClient.LOG.debug("newInfo = " + newInfo);
  7. }
  8. if (newInfo == null) {
  9. throw new IOException("Cannot open filename " + src);
  10. }
  11.  
  12. if (locatedBlocks != null) {
  13. Iterator<LocatedBlock> oldIter = locatedBlocks.getLocatedBlocks().iterator();
  14. Iterator<LocatedBlock> newIter = newInfo.getLocatedBlocks().iterator();
  15. while (oldIter.hasNext() && newIter.hasNext()) {
  16. if (! oldIter.next().getBlock().equals(newIter.next().getBlock())) {
  17. throw new IOException("Blocklist for " + src + " has changed!");
  18. }
  19. }
  20. }
  21. locatedBlocks = newInfo;
  22. long lastBlockBeingWrittenLength = 0;
  23. if (!locatedBlocks.isLastBlockComplete()) {
  24. final LocatedBlock last = locatedBlocks.getLastLocatedBlock();
  25. if (last != null) {
  26. if (last.getLocations().length == 0) {
  27. if (last.getBlockSize() == 0) {
  28. return 0;
  29. }
  30. return -1;
  31. }
  32. final long len = readBlockLength(last);
  33. last.getBlock().setNumBytes(len);
  34. lastBlockBeingWrittenLength = len;
  35. }
  36. }
  37.  
  38. fileEncryptionInfo = locatedBlocks.getFileEncryptionInfo();
  39. //返回block开始写的位置
  40. return lastBlockBeingWrittenLength;
  41. }

  回到DFSClient中:

  

  1. DFSClient.java
  2. @VisibleForTesting
  3. public LocatedBlocks getLocatedBlocks(String src, long start, long length)
  4. throws IOException {
  5. TraceScope scope = getPathTraceScope("getBlockLocations", src);
  6. try {
  7. //这里NameNode作为参数传递到callGetBlockLocations()中
  8. return callGetBlockLocations(namenode, src, start, length);
  9. } finally {
  10. scope.close();
  11. }
  12. }

  调用服务端方法,返回block信息:

  1. //DFSClient.java
  2. static LocatedBlocks callGetBlockLocations(ClientProtocol namenode,
  3. String src, long start, long length)
  4. throws IOException {
  5. try {
  6. //看到这里,不用做过多的解释了吧?
  7. return namenode.getBlockLocations(src, start, length);
  8. } catch(RemoteException re) {
  9. throw re.unwrapRemoteException(AccessControlException.class,
  10. FileNotFoundException.class,
  11. UnresolvedPathException.class);
  12. }
  13. }

  最终将文件block相关信息写入输入流,通过工具类IOUtil输出到本地文件。

  那关于hadoop之hdfs原理及文件上传下载源码解析就写到这里,下系列的文章,楼主会写一些关于mapreduce或者hive相关的文章分享给大家。

  示例代码地址:https://github.com/LJunChina/hadoop

  

  

Hadoop之HDFS原理及文件上传下载源码分析(下)的更多相关文章

  1. Hadoop之HDFS原理及文件上传下载源码分析(上)

    HDFS原理 首先说明下,hadoop的各种搭建方式不再介绍,相信各位玩hadoop的同学随便都能搭出来. 楼主的环境: 操作系统:Ubuntu 15.10 hadoop版本:2.7.3 HA:否(随 ...

  2. php实现文件上传的源码

    php实现文件上传的源码,更多php技术开发就去php教程网,http://php.662p.com <?php ##author :Androidyue ##sina @androidyue ...

  3. 构建ASP.NET MVC4+EF5+EasyUI+Unity2.x注入的后台管理系统(32)-swfupload多文件上传[附源码]

    原文:构建ASP.NET MVC4+EF5+EasyUI+Unity2.x注入的后台管理系统(32)-swfupload多文件上传[附源码] 文件上传这东西说到底有时候很痛,原来的asp.net服务器 ...

  4. swfupload多文件上传[附源码]

    swfupload多文件上传[附源码] 文件上传这东西说到底有时候很痛,原来的asp.net服务器控件提供了很简单的上传,但是有回传,还没有进度条提示.这次我们演示利用swfupload多文件上传,项 ...

  5. ASP.NET MVC5+EF6+EasyUI 后台管理系统(32)-swfupload多文件上传[附源码]

    系列目录 文件上传这东西说到底有时候很痛,原来的asp.net服务器控件提供了很简单的上传,但是有回传,还没有进度条提示.这次我们演示利用swfupload多文件上传,项目上文件上传是比不可少的,大家 ...

  6. web大文件上传断点续传源码

    总结一下大文件分片上传和断点续传的问题.因为文件过大(比如1G以上),必须要考虑上传过程网络中断的情况.http的网络请求中本身就已经具备了分片上传功能,当传输的文件比较大时,http协议自动会将文件 ...

  7. PHP大文件上传断点续传源码

    文件夹数据库处理逻辑 publicclass DbFolder { JSONObject root; public DbFolder() { this.root = new JSONObject(); ...

  8. element-ui Upload 上传组件源码分析整理笔记(十四)

    简单写了部分注释,upload-dragger.vue(拖拽上传时显示此组件).upload-list.vue(已上传文件列表)源码暂未添加多少注释,等有空再补充,先记下来... index.vue ...

  9. .net大文件上传断点续传源码

    IE的自带下载功能中没有断点续传功能,要实现断点续传功能,需要用到HTTP协议中鲜为人知的几个响应头和请求头. 一. 两个必要响应头Accept-Ranges.ETag 客户端每次提交下载请求时,服务 ...

随机推荐

  1. JS中一些常用的内置对象

    在JS中,经常会遇到明明知道一个对象有某个属性或方法,可是又不知道怎么写的情况.下面,我就罗列了一些JS中常用的内置对象的属性和方法. Math对象: Math对象的作用是执行常见的算术任务. 首先M ...

  2. StringBuffer与StringBuilder的区别,及实现原理

    区别 1.StringBuffer 与 StringBuilder 中的方法和功能完全是等价的, 2.只是StringBuffer 中的方法大都采用了 synchronized 关键字进行修饰,因此是 ...

  3. MyBastis初次环境配置讲解

    MyBatis 本是apache的一个开源项目iBatis, 2010年这个项目由apache software foundation 迁移到了google code,并且改名为MyBatis .20 ...

  4. 轻松理解JavaScript闭包

    摘要 闭包机制是JavaScript的重点和难点,本文希望能帮助大家轻松的学习闭包 一.什么是闭包? 闭包就是可以访问另一个函数作用域中变量的函数. 下面列举出常见的闭包实现方式,以例子讲解闭包概念 ...

  5. ubuntu firefox上看视频,安装flash啊

    这是针对于直接硬盘安装的linux系统: u盘安装选择了安装第三方软件的话就不会存在这种问题 flash的安装其实也不是很难的,有点耐心就ok了 总结一下: 1:肯定是下载最新版的flash啦,注意看 ...

  6. C#基础笔记---浅谈XML读取以及简单的ORM实现

    背景: 在开发ASP.NETMVC4 项目中,虽然web.config配置满足了大部分需求,不过对于某些特定业务,我们有时候需要添加新的配置文件来记录配置信息,那么XML文件配置无疑是我们选择的一个方 ...

  7. React-Native 开发(二) 在react-native 中 运用 redux

    前提: 一个小web前端,完全不会android 跟iOS 的开发,首次接触,有很多不懂的问题.请见谅. 环境: win7 上一篇 : React-Native 开发(一) Android环境部署,H ...

  8. xgboost-python参数深入理解

    由于在工作中应用到xgboost做特征训练预测,因此需要深入理解xgboost训练过程中的参数的意思和影响. 通过search,https://www.analyticsvidhya.com/blog ...

  9. 【VB超简单入门】五、基本输出输入

    之前讲了VB IDE的基本操作和概念,接下来要开始将VB语言的编程了. 程序最重要的部分是输出和输入,输入数据,经过计算机处理,再输出结果.本文将介绍两种最基本的输出输入方法,分别是Print.Msg ...

  10. 关于Android开发的几点建议

    绝不要在UI线程中做数据处理的工作,这会让你的app变慢,带来极差的用户体验. 要按照google发布的Design指导意见来设计app,比如一个holo主题app会给用户带来更好的用户体验. 不要复 ...