ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

HDFS读写原理与Java API实战:从NameNode到DataNode的完整链路

HDFS读写原理与Java API实战:从NameNode到DataNode的完整链路 简介《云计算技术实验报告四HDFS文件的读写》是一份计算机科学专业《云计算技术》课程的实验报告面向正在学习Hadoop生态系统的学生重点演示HDFS分布式文件系统文件的上传、下载与合并编程实现。资源包共1个文件为PDF文档大小约1.1MB内容完整。已有561人浏览学习是云计算方向备受关注的实验资料。报告以PutMerge和GetMerger两个功能为主线记录了在Linux环境下使用Eclipse连接Hadoop集群的配置过程包括插件部署、环境参数设置、项目创建以及Java程序编写等环节并针对实际运行中可能遇到的权限、网络和版本兼容问题给出了排错参考。实验过程涉及文件系统接口、目录列表方法等关键知识帮助读者理解HDFS文件合并到本地的完整流程有效将理论知识点与实际操作结合起来。这份报告适合作为云计算实验课的参考案例也可供自学分布式存储技术的人士借鉴能够提升环境配置与问题排查能力。1. 云计算实验里的HDFS读写为什么值得单独写一份报告很多人第一次做云计算技术实验里的HDFS文件读写时会觉得“不就是把文件放上去、再拿下来吗”。真动手才发现要么文件写进去后在另一个节点上查不到要么写入过程中集群日志弹出一堆租约异常要么代码里调用了FileSystem.get却一直连不上NameNode。这个实验四偏偏是整个HDFS学习路径里最容易翻车也最值得做透的一环它把分布式文件系统的元数据管理、数据落盘、副本复制、租约机制全部串在了一次简单的读写操作里。这份实验不只是验证“能读能写”它要做的是让操作者清楚一条数据从客户端出发经过NameNode调度、DataNode流水线复制再到另一个客户端读回的全部动作。适合两类人一类是正在做云计算课程实验、需要交付报告的学生另一类是刚接触Hadoop生态、想用最小代价搞懂分布式存储读写链路的从业者。读完你不仅能把实验跑通还能知道参数怎么调、故障怎么看。2. HDFS读写流程数据从客户端到DataNode中间发生了什么2.1 NameNode与DataNode读和写分别由谁干活要理解HDFS读写第一步是把集群里两个角色的分工刻进脑子里NameNode管元数据DataNode管数据块。写文件时客户端先跟NameNode打交道NameNode告诉客户端“这个文件的前几个块应该写到哪几个DataNode上”然后客户端才真正把数据传给DataNode。读文件时反过来客户端向NameNode问“文件的某个块在哪些DataNode上”NameNode返回块位置列表客户端直接到DataNode上去拉数据。这个分工里有个经常被忽略的点文件的真实数据从不经过NameNode。很多人在实验里把DataNode节点停了然后发现文件还能读就以为HDFS读操作不依赖DataNode——其实那是因为文件的副本还在另一个DataNode上NameNode只是把块位置重新解析了一遍。如果所有副本都丢了NameNode再完整也无济于事。另一个容易误解的是写入时的数据流方向。客户端不是把文件整体上传给NameNode再分发出去的而是按块Block切分逐个块地建立一条写入管线。常见的默认块大小是128MB文件只有几十KB时只占一个块但写入时仍会经历“向NameNode申请块位置、建立管线、逐包写入、确认收到”的完整流程。2.2 一条写入命令在HDFS里实际走了哪几步以一条最简单的方式来看HDFS读写流程的实际运转。假设用一个命令行指令写入文件完整链路大致是客户端调用DistributedFileSystem.create向NameNode发起创建请求NameNode检查权限和目录在内存元数据里登记文件条目返回一个FSDataOutputStream客户端准备数据数据被切分成数据包packet沿管线传给第一个DataNode第一个DataNode落盘后把包转发给第二个DataNode第二个落盘后再转发给第三个同时逐级向上返回确认包。所有确认都收到后客户端才认为这一个包写完。最后客户端调用closeNameNode收到完成信号文件才从“正在构建”变成“已关闭”。这个过程可以用一条命令直接观察到hdfs dfs -put /etc/hosts /user/student/exp4_hosts.txt命令执行完毕后用目录列表确认文件确实建出来了hdfs dfs -ls /user/student/执行结果里会看到副本数一列。后面可以用fsck命令检查块的分布情况这部分在第六章展开。上面这条命令的逻辑很简单把本地文件推送到HDFS指定路径。关键在于put命令背后触发的动作是“创建写入关闭”三合一任何一步没完成ls里都可能看不到文件或者看到一个大小为0的残留项——实验报告里贴上这个细节比贴十行输出更有说服力。参数方面put命令本身没有太多可调的但它的行为受集群的dfs.replication影响。实验环境里如果该值是3文件会被复制到3个DataNode上如果只想写一份副本可以临时指定hdfs dfs -D dfs.replication1 -put /etc/hosts /user/student/exp4_hosts_single.txt这条命令用-D参数临时覆盖副本数不修改集群全局配置适合实验里对比副本数与存储占用关系时使用。3. 用Java API跑通最小读写案例实验报告的核心代码3.1 最小写入案例FileSystem、FSDataOutputStream与租约命令行能说明HDFS读写流程但实验报告真正拿得出手的是用Java API完成读写。Java API是HDFS最原始的编程入口本身没有经过任何封装能看到最完整的调用链。很多框架像Spark、Hive在读写HDFS时底层走的也是这一套API只是包了一层接口。以下是能直接编译运行的最小写入代码适用于Hadoop 3.x版本实验环境如果是2.x包路径也不变import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.fs.FSDataOutputStream; import org.apache.hadoop.fs.FileSystem; import org.apache.hadoop.fs.Path; public class HdfsWriteDemo { public static void main(String[] args) throws Exception { // 创建配置对象加载core-site.xml等默认配置 Configuration conf new Configuration(); // 注意这里的URI需要改成你的NameNode地址和端口 conf.set(fs.defaultFS, hdfs://namenode:8020); // 获取文件系统实例等价于拿到HDFS客户端的入口 FileSystem fs FileSystem.get(conf); // 目标路径/user/student/exp4_write.txt Path dst new Path(/user/student/exp4_write.txt); // 创建文件并获取输出流第二个参数true表示覆盖已有文件 FSDataOutputStream out fs.create(dst, true); // 写入三行文本注意write接收的是字节数组 String content hello hdfs write\nthis is exp4\n; out.write(content.getBytes(UTF-8)); // 关键关闭输出流触发close操作文件才真正完成 out.close(); System.out.println(write success); // 关闭文件系统实例释放客户端连接资源 fs.close(); } }这段代码的逻辑并不复杂先拿到FileSystem实例再create一个文件、写入字节、关闭流。值得在实验报告里解释的是最后一步close。在HDFS的写入模型里没调用close前文件处于构建状态NameNode不会把它标记为“已完成”此时另一个客户端尝试读这个文件会收到租约过期或文件未关闭的异常。实验里常见的问题是写了数据但忘了关流程序退出了但文件在HDFS上显示为0字节原因就在这一步。编译运行前需要把Hadoop客户端的jar包放进classpath。常见做法是直接用hadoop命令跑主类它会自动带上全部依赖hadoop jar HdfsWriteDemo.jar HdfsWriteDemo如果环境里没打jar包也可以用javac指定classpath编译路径指向$HADOOP_HOME/share/hadoop/common和hdfs目录下的jarjavac -classpath $(hadoop classpath) HdfsWriteDemo.java前一种做法更省事强烈建议在实验报告里用hadoop jar方式避免一堆jar包依赖写进编译命令导致看起来复杂。3.2 读回与校验FSDataInputStream与seek的边界写入完成后的常规操作是把它读回来比对这是实验报告里体现验证意识的地方。读跟写的代码结构类似但有个API使用细节值得写进知识点import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.fs.FSDataInputStream; import org.apache.hadoop.fs.FileSystem; import org.apache.hadoop.fs.Path; public class HdfsReadDemo { public static void main(String[] args) throws Exception { Configuration conf new Configuration(); conf.set(fs.defaultFS, hdfs://namenode:8020); FileSystem fs FileSystem.get(conf); Path src new Path(/user/student/exp4_write.txt); FSDataInputStream in fs.open(src); byte[] buffer new byte[1024]; int len in.read(buffer); String result new String(buffer, 0, len, UTF-8); System.out.println(read content: result); // seek到文件开头重新读验证HDFS流的随机读能力 in.seek(0); len in.read(buffer); System.out.println(after seek: new String(buffer, 0, len, UTF-8)); in.close(); fs.close(); } }read返回的是实际读到的字节数如果文件内容是“hello hdfs write\nthis is exp4\n”buffer大小设为1024时一次就能读完。这里故意演示了seek(0)回到文件开头再读一次目的是说明HDFS的输入流支持定位读这在后续处理SequenceFile或Parquet文件时非常关键——MapReduce和Spark的InputSplit定位就是靠这个能力实现的。做实验时建议把两个程序合并写在一起先写后读并比对字符串是否一致这样报告的数据只有一份、流程更完整。不过要注意重复运行写入程序的时候第二个参数true会覆盖原文件如果不确定文件是否写过可以先调用fs.exists判断一下避免误覆盖。4. hdfs常用命令对照与副本参数设置从实验到实用4.1 命令行操作对照put、cat、tail、stat各管什么场景Java API是内核命令行是日常巡检和实验验证最快的路。做云计算实验时几步简单指令往往能解决“代码是否真把文件写进去了”的疑问不用每次打开IDE跑一遍。下面这张表整理了实验里最常用、报告里最常引用的hdfs常用命令命令作用典型使用场景hdfs dfs -put 本地路径 HDFS路径把本地文件上传到HDFS上传实验数据或测试文件hdfs dfs -cat HDFS路径把文件内容打印到终端快速查看小文件内容hdfs dfs -tail HDFS路径查看文件末尾1KB内容检查追加写入是否生效hdfs dfs -stat 格式 HDFS路径按指定格式输出文件状态查块大小、修改时间、副本数hdfs dfs -setrep -R 副本数 HDFS路径修改已存在文件的副本数降低副本数清理存储空间hdfs dfs -get HDFS路径 本地路径从HDFS下载文件到本地验证读链路是否正常这些命令的作用和参数都清楚真正容易踩坑的是stat命令。很多人想查一个文件的实际字节数直接输入hdfs dfs -stat /path得到的是空结果。原因是stat命令默认输出空格式必须指定格式化参数才可以。例如hdfs dfs -stat %b %o %r %n /user/student/exp4_write.txt这个命令依次输出文件字节数、块大小、副本数和文件名。对照实验报告时看到输出里“块大小”显示的是128M或256M这样的值不要疑惑为什么一个小文件占这么大块——HDFS只按块分配存储位置实际占用按文件大小计算。想看数据块的分布位置用fsck定位hdfs fsck /user/student/exp4_write.txt -files -blocks -locations输出里会列出这个文件的每个块落在哪些DataNode上。实验报告里截取这段输出能非常直观地说明副本机制。加-locations参数后输出还包括存储块的DataNode主机名和IP这正好用来验证3副本是否分布在3个不同节点上。如果集群只有1个DataNode那3个副本会落在同一台机器的3块磁盘目录里这也是合理情况。4.2 副本数与块大小怎么设从1到3的真实差别HDFS写入时牵扯到两个高频参数dfs.replication与dfs.blocksize。前者控制每个数据块被复制几份后者控制文件切片块大小。实验环境里NameNode和DataNode都在同一台机器上时把副本数设为3并没有意义只是徒增磁盘占用但教学场景往往刻意保留默认值3目的就是让你在实验报告里写清楚“3副本机制与故障容错的关系”。设参数有三种层级集群全局配置、命令临时指定、Java API里Job级别设置。实验里常见的是写代码时希望通过API改变副本数比如// 通过conf对象设置副本数为1只对这一份程序生效 conf.set(dfs.replication, 1);这段代码要在FileSystem.get之前调用才有效。但有一个隐藏坑如果你先创建了FileSystem实例再在后面的代码里调用fs.setReplication(path, (short) 1)效果虽然也是修改副本数但它是异步触发的DataNode要等下一次心跳才会执行多余的副本删除或复制。实验里写完程序立刻查看副本数没变化多半就是没弄明白这两种设置方式的生效时机。块大小的设置同样有层级问题。Java API里可以用conf.set(dfs.blocksize, 134217728)指定128M命令行传文件时可以用-D参数指定hdfs dfs -D dfs.blocksize1048576 -put /etc/hosts /user/student/small_block.txt这里1048576即1MB适合上传小文件后查看块分布。实际工作中块大小不建议动保持128M或256M就行但实验报告里演示一次1MB小块的写入能让你直观地看到“一个文件被切成多个块”这件事文件超过1MB时fsck输出里会出现多个block记录这比空讲概念有说服力得多。Python方式也值得在报告里提一笔。实验环境装了pandas或pyarrow时可以用简洁代码读写HDFS上的文本文件import pandas as pd # 读取HDFS上的CSV文件路径用完整HDFS地址 df pd.read_csv(hdfs://namenode:8020/user/student/exp4_data.csv) # 写回HDFSindexFalse避免写出多余索引列 df.to_csv(hdfs://namenode:8020/user/student/exp4_out.csv, indexFalse)这段代码看起来简单但它能跑通的前提是pyarrow或fsspec已经安装且集群RPC端口能连通。很多实验里遇到“pandas读不到HDFS”的问题原因不是pandas本身的语法而是它依赖的C扩展库没装全。所以我把这个写法放在Java API之后——原理先懂工具才不会用错。5. 踩坑与排查HDFS读写实验最常见的5个翻车现场5.1 文件写入成功但读出来是空的没关流现象Java程序执行完没有报错HDFS目录里也确实列出了目标文件但文件大小显示0字节代码里打印读出内容为空白。原因创建文件并获得FSDataOutputStream之后如果没有调用close输出流不会把最后的flush和close操作交给NameNode确认文件的元数据迟迟无法标记为“已完成”。很多初学代码里习惯只调write不调close以为进程退出时资源会自动释放。JVM结束进程时确实会释放操作系统层面的句柄但HDFS服务端不知道客户端意外退出的具体原因只能等租约超时回收这个时间可能长达几分钟。解决在写入逻辑的最后显式调用out.close()并且最好把close放进finally块避免业务逻辑抛异常时跳过关闭动作。FSDataOutputStream out null; try { out fs.create(dst, true); out.write(content.getBytes(UTF-8)); } finally { if (out ! null) { out.close(); } }注意这里不要用try-with-resources或用复制的写法Hadoop 3.x的FSDataOutputStream实现了Closeable直接放在try资源列表里也可以。关键是养成“关闭流”的意识实验过程里一半的0字节文件都是这个原因。5.2 启动后put文件报“租约过期”或“文件被并发写”现象集群刚启动准备往/user/student目录传文件命令行先是卡住一段时间随后报出类似“Lease mismatch”或“Failed to replace a bad datanode”的错误。原因这个情况最常见的原因是上一个实验或上一次运行的程序异常退出没有释放文件的写租约。HDFS里一个文件同一时刻只允许一个客户端写入写租约未过期前新客户端无法拿同一个路径的写权限。租约过期时间有默认配置但不会在读操作时自动触发需要等服务端检查。解决先确认是不是真有人还在写同一个文件然后等待租约超时或者用管理员身份直接删除残留文件重建。实验环境里通常直接执行hdfs dfs -rm -f /user/student/exp4_write.txt把残留文件删除后重新put即可。如果频繁遇到租约过期检查代码里是否有线程未退出、连接池没有释放的问题。5.3 设置setReplication后副本数没变现象写入后用Java代码调用fs.setReplication(path, (short) 1)紧接着用hdfs dfs -ls查看副本数仍然显示3。原因setReplication是异步操作。它只是给NameNode发一条元数据变更请求NameNode将这个变更记录到编辑日志真正的副本删除或复制要等DataNode的心跳上报后才能执行。心跳周期默认是3秒但加上调度延迟实际生效可能要到几秒甚至更久。解决不要立刻查看副本状态等待心跳周期后再查询。如果是为了实验演示最直接的做法是在写入前就把conf.set(dfs.replication, 1)设置好让文件从创建起就按1副本写入这样ls和fsck的输出立刻一致。5.4 put大文件卡在写阶段不结束现象上传一个几百MB的文件进度百分比长时间停在同一个数字日志里没有任何报错。原因通常不是HDFS本身的问题而是客户端本地磁盘空间不足或网络传输瓶颈。HDFS客户端写入时要先把本地文件切分成数据包数据包在内存或落盘临时文件中缓冲如果客户端机器/tmp目录写满写入就会停顿。还有一种情况是dfs.client.block.write.replace-datanode-on-failure参数触发了慢节点替换管线重建耗时较长。解决先看客户端本地磁盘剩余空间df -h /tmp如果满了清理临时文件或者设置hadoop.tmp.dir到另一个分区。再看集群网络确认DataNode之间的带宽不是1Gbps共享口。实验环境里没有条件调网络通常就是清理本地临时文件最快。5.5 Python读HDFS报错pandas或pyarrow装了就绪但依然失败现象用pandas.read_csv读hdfs://路径报错信息要么提到“Unsupported filesystem”要么是“java.io.IOException”或者干脆是“gzip”相关异常。原因HDFS没有标准的POSIX文件系统接口pandas默认用fsspec识别路径。fsspec要正确读HDFS需要安装pyarrow或者hdfs库并且这些库的版本要和本机Java环境兼容。另一个常见原因是把HDFS上的SequenceFile或Avro文件当成普通文本读取文件内部结构不是纯文本pandas自然会解析失败。解决先用命令行确认文件确实是纯文本hdfs dfs -cat /user/student/exp4_data.csv | head -5能正常打印内容再交给pandas。区分是文件格式问题还是客户端依赖问题。如果命令行能读、Python读取不了才判断是依赖缺失再补装pyarrow并确认libhdfs可用。6. 验证与进阶用fsck和DataNode日志确认数据真的落盘了6.1 fsck输出里的三层信息怎么读实验做完不要急着写报告先做一次完整性验证。HDFS自带fsck命令可以检查文件是否有损坏块、副本是否达到期望值、数据块是否均匀分布。这条命令是每个写过HDFS程序的人都该熟练使用的基本功hdfs fsck /user/student/exp4_write.txt -files -blocks -locations执行结果里主要看三块内容。第一块是“Total size”显示文件总字节数可以跟本地源文件对比确认数据没丢。第二块是“Number of blocks”这个数字等于文件字节数除以块大小向上取整如果文件只有几十KB显示1是正常的。第三块是每个block下面的Datanode列表正常情况下期望副本数是多少列表里就应该有几个节点。如果看到某个块的Datanode列表数量小于期望副本数说明有副本丢了需要关注集群健康状况。做验证时建议把本地源文件的md5值和读回的HDFS文件md5值做一次对比# 本地文件校验 md5sum /etc/hosts # 拉回HDFS上的文件后校验 hdfs dfs -get /user/student/exp4_write.txt /tmp/exp4_check.txt md5sum /tmp/exp4_check.txt两个md5值一致比打印几行文本内容更能说明读写链路完整。这个技巧在实验报告里非常加分它证明了你验证的不仅是“能读”还有“读到的内容确实是写进去的内容”。6.2 看DataNode写盘日志把HDFS当成黑匣子不如直接看日志HDFS客户端执行写入时数据最终落在DataNode的数据目录里。实验过程里如果怀疑文件没有真正写到磁盘或者想搞明白写入动作发生在哪个节点可以看DataNode的运行日志。日志文件默认在$HADOOP_HOME/logs目录下文件名类似hadoop-hadoop-datanode-主机名.loggrep exp4_write $HADOOP_HOME/logs/*datanode*.log | tail -20通过日志能看到DataNode收到块写入请求的记录包括块ID、长度、最终校验结果。如果发现日志里有“Checksum mismatch”之类的内容说明数据传输过程中发生了损坏HDFS会自动从其他副本重新复制数据这个过程不用人工干预但在实验报告里记录下这个现象能体现你对容错机制的理解。还可以用datanode日志验证小文件写入时块ID的变化规律连续两次写入同一个文件块ID不同重复覆盖写入旧块ID会在日志里出现删除记录。这种细节只看客户端界面是发现不了的。养成看日志的习惯之后很多“玄学”问题都能快速定位。比如文件写了一半集群断电重启后某个DataNode起不来日志里通常直接告诉你磁盘目录权限异常。先把日志翻一遍胜过重启集群十次。我自己的做法是每次实验开始前先在三个终端分别打开NameNode、DataNode和客户端日志的tail窗口写文件时观察三个终端的实时滚动输出。这比事后排查效率高得多也能让你把HDFS读写流程串得像条链而不是几个孤立节点。希望这个习惯对你也管用。本文还有配套的精品资源点击获取
返回列表