尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Hadoop数据云盘项目实战:HDFS存储模型与断点续传实现

Hadoop数据云盘项目实战:HDFS存储模型与断点续传实现 简介这份资源是面向高校学生与大数据初学者的Hadoop项目实战资料以“数据云盘”为业务场景帮助读者在课程设计、期末大作业或自学练手中完成一套可运行、可讲解的完整系统。项目采用Java Web技术栈结合Hadoop生态实现文件存储与管理功能代码注释较为充分新手也能对照理解整体流程。压缩包共126个文件约58.11MB其中32个java源文件构成后端核心逻辑19个js与11个css负责前端交互与页面样式另有10个jsp页面、10个xml配置及7个jar依赖包并附带properties配置、字体图标与说明文档部署后即可直接使用。目前已有1153人学习下载说明其作为课程设计参考具有较高认可度。读者可获得完整项目源码、数据库与配置说明、界面资源及模块划分清晰的目录结构便于快速搭建环境、理解数据云盘的文件上传下载与后台管理实现并在此基础上完成二次开发或撰写项目报告。1. 数据云盘项目为什么成了 Hadoop 课程设计的硬通货打开任何一个大数据毕业设计选题清单Hadoop 相关的题目永远占大头而数据云盘几乎是其中出现频率最高的落地场景。原因不复杂它同时踩中了文件存储、元数据管理、用户权限、断点续传这几个大数据入门必考的知识点又不像推荐系统那样依赖算法调参一个本科生用一台笔记本跑伪分布式就能把整条链路走通。我带过几届课程设计学生最容易翻车的地方从来不是代码写不出来而是环境没搭对、HDFS 的块大小和副本数乱设、上传大文件时 NameNode 直接卡死。这篇笔记就围绕一个完整的 Hadoop 数据云盘项目把从零搭建到核心功能实现再到排错的全过程拆开讲源码结构和文档说明该覆盖什么也一并说清楚。适合正在做大数据课程设计、毕业设计或者想拿一个真实项目练手 HDFS API 的开发者。读完你至少能自己跑通一套可演示、可答辩、代码能讲明白的数据云盘。2. 动手之前先把 HDFS 存储模型和项目架构定下来2.1 数据云盘到底在 HDFS 上存了什么很多人一上来就写上传下载代码结果连文件在 HDFS 里长什么样都说不清。Hadoop 分布式文件系统的存储逻辑是一个文件被切成固定大小的数据块block默认 128MB每个块存多份副本因子默认 3分散在不同 DataNode 上。NameNode 只负责记录「哪个文件由哪些块组成、这些块在哪些节点上」真正的文件内容全在 DataNode 的本地磁盘里。数据云盘项目本质上是在这套机制上加了一层业务封装。用户看到的「文件夹」「文件列表」「分享链接」对应的是 HDFS 上的目录结构和文件路径用户上传一个 500MB 的视频HDFS 会自动切成 4 个块128128128116分别落到不同 DataNode。你的 Java 或 Python 代码调用的FileSystem.create()和FSDataOutputStream.write()底层就是在跟 NameNode 要块分配、跟 DataNode 传数据。理解这一点之后项目里几个关键设计就有依据了为什么用户目录要用/user/用户名/这种前缀因为 HDFS 本身就是树形结构天然支持按路径隔离。为什么删除文件要先检查回收站因为 HDFS 的delete()默认是直接删不给后悔药业务层得自己加一层软删除。2.2 项目分层Web 层、服务层、存储层怎么切一个能拿得出手的数据云盘项目代码结构不能是一坨。常见的分层方式是三层层次职责典型技术选型Web 层接收 HTTP 请求、页面渲染、文件流输出Spring Boot / Servlet JSP服务层用户认证、权限校验、文件元数据管理Java Service 类 / Spring Service存储层实际读写 HDFS、管理目录和块Hadoop FileSystem API服务层和存储层之间建议加一个 DAO 或 Repository 接口把 HDFS 操作封装成upload()、download()、list()、mkdir()、rename()、delete()这几个原子方法。这样做的好处是后面如果要换存储后端比如从 HDFS 换成 MinIO只需要改存储层实现上层业务代码不动。元数据管理是另一个容易忽略的点。HDFS 本身不存「文件是谁上传的、上传时间、文件大小、是否公开」这些业务属性。你需要一张 MySQL 表来记录CREATE TABLE file_meta ( id BIGINT PRIMARY KEY AUTO_INCREMENT, user_id BIGINT NOT NULL, hdfs_path VARCHAR(512) NOT NULL, file_name VARCHAR(255) NOT NULL, file_size BIGINT DEFAULT 0, is_dir TINYINT DEFAULT 0, upload_time DATETIME DEFAULT CURRENT_TIMESTAMP, is_deleted TINYINT DEFAULT 0, INDEX idx_user (user_id), INDEX idx_path (hdfs_path(255)) );hdfs_path存文件在 HDFS 上的绝对路径is_deleted做软删除标记。查询文件列表时先查这张表拿到路径再去 HDFS 确认文件是否存在两边对不上就以 HDFS 为准做修复。这个设计在答辩时是加分项因为它体现了你对「元数据和实际存储可能不一致」这个真实问题的思考。2.3 伪分布式环境搭建从零到能跑通一条上传命令课程设计阶段用伪分布式就够了一台机器同时跑 NameNode、DataNode、ResourceManager。下面是从零开始的步骤假设你用 Ubuntu 20.04 或 CentOS 7。第一步装 JDK 并配环境变量。Hadoop 3.x 要求 JDK 8 或 11sudo apt update sudo apt install openjdk-8-jdk -y java -version # 输出应为 openjdk version 1.8.0_xxx第二步下载 Hadoop 并解压。官网下载二进制包不要下源码包cd /opt sudo wget https://archive.apache.org/dist/hadoop/common/hadoop-3.3.6/hadoop-3.3.6.tar.gz sudo tar -xzf hadoop-3.3.6.tar.gz sudo mv hadoop-3.3.6 hadoop sudo chown -R $USER:$USER /opt/hadoop第三步配置四个核心文件。etc/hadoop/core-site.xml指定 NameNode 地址configuration property namefs.defaultFS/name valuehdfs://localhost:9000/value /property property namehadoop.tmp.dir/name value/opt/hadoop/data/tmp/value /property /configurationhdfs-site.xml设副本数为 1伪分布式只有一个 DataNode设 3 会一直报副本不足configuration property namedfs.replication/name value1/value /property property namedfs.namenode.name.dir/name value/opt/hadoop/data/name/value /property property namedfs.datanode.data.dir/name value/opt/hadoop/data/data/value /property /configurationmapred-site.xml和yarn-site.xml按默认模板改一下即可课程设计阶段不跑 MapReduce 任务的话影响不大。第四步格式化 NameNode 并启动hdfs namenode -format start-dfs.sh jps # 应该看到 NameNode、DataNode、SecondaryNameNode 三个进程第五步验证hdfs dfs -mkdir -p /user/cloudpan hdfs dfs -ls / echo hello hadoop test.txt hdfs dfs -put test.txt /user/cloudpan/ hdfs dfs -cat /user/cloudpan/test.txt能正常输出hello hadoop就说明存储层通了。这一步看着简单但每年都有学生卡在这里——要么是hadoop.tmp.dir没配导致格式化后数据丢在/tmp被系统清理要么是 9000 端口被占用要么是 SSH 免密没配导致start-dfs.sh一直要密码。SSH 免密用ssh-keygen -t rsa加ssh-copy-id localhost两条命令解决。3. 用 Java FileSystem API 把上传下载断点续传写出来3.1 连接 HDFS 的最小可用代码在 Eclipse 或 IDEA 里新建 Maven 项目pom.xml引入 Hadoop 客户端依赖。注意版本要和你服务端装的 Hadoop 版本一致否则会出现NoSuchMethodError这种玄学问题dependency groupIdorg.apache.hadoop/groupId artifactIdhadoop-client/artifactId version3.3.6/version /dependency连接 HDFS 的核心代码import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.fs.FileSystem; import org.apache.hadoop.fs.Path; import java.net.URI; public class HdfsUtil { private static final String HDFS_URI hdfs://localhost:9000; private static FileSystem fs; public static FileSystem getFS() throws Exception { if (fs null) { Configuration conf new Configuration(); // 关键参数客户端写数据时的管道超时大文件上传必调 conf.set(dfs.client.socket-timeout, 60000); conf.set(dfs.datanode.socket.write.timeout, 60000); fs FileSystem.get(new URI(HDFS_URI), conf, hadoop); } return fs; } }FileSystem.get()的第三个参数是用户名不传的话默认用当前系统用户在 Windows 上开发时经常因为用户名不匹配导致权限拒绝。dfs.client.socket-timeout默认是 60 秒上传大文件时如果网络抖动容易超时调到 120000 更稳。3.2 上传从本地流到 HDFS 流的完整链路上传的核心逻辑是本地文件输入流 → 缓冲区 → HDFS 输出流。不要一个字节一个字节写那样慢到怀疑人生public static void upload(String localPath, String hdfsPath) throws Exception { FileSystem fs getFS(); Path src new Path(localPath); Path dst new Path(hdfsPath); // 缓冲区设为 4MB平衡内存和速度 byte[] buffer new byte[4 * 1024 * 1024]; try (InputStream in new FileInputStream(localPath); FSDataOutputStream out fs.create(dst, true)) { // true 表示覆盖已存在文件 int len; while ((len in.read(buffer)) ! -1) { out.write(buffer, 0, len); } out.hflush(); // 强制刷出确保数据落到 DataNode } // 上传成功后写元数据表 FileMeta meta new FileMeta(); meta.setHdfsPath(hdfsPath); meta.setFileSize(fs.getFileStatus(dst).getLen()); meta.setUploadTime(new Date()); fileMetaDao.insert(meta); }fs.create(dst, true)的第二个参数是 overwrite设为 true 时同名文件直接覆盖。如果你要做「同名文件自动重命名」就设 false 然后捕获FileAlreadyExistsException在 catch 里给文件名加时间戳后缀。out.hflush()和out.hsync()的区别值得说一句hflush保证数据到达 DataNode 的内存hsync保证落到磁盘。课程设计用hflush够了追求极致可靠性才用hsync但性能会掉一截。3.3 下载与断点续传Range 请求怎么落到 HDFS 的 seek下载比上传简单但断点续传是加分项。HTTP 协议用Range头指定字节范围服务端要做的就是FSDataInputStream.seek(offset)然后从那个位置开始读public static void download(String hdfsPath, long offset, OutputStream responseOut) throws Exception { FileSystem fs getFS(); Path path new Path(hdfsPath); FileStatus status fs.getFileStatus(path); long fileLen status.getLen(); try (FSDataInputStream in fs.open(path)) { in.seek(offset); // 跳到断点位置 byte[] buffer new byte[8 * 1024]; int len; while ((len in.read(buffer)) ! -1) { responseOut.write(buffer, 0, len); } } }Web 层收到请求后从Range: bytes1024000-里解析出 offset设置响应头Content-Range: bytes 1024000-4999999/5000000和状态码 206再把 HDFS 流写回客户端。浏览器或下载工具看到 206 就知道支持续传下次断网重连会带上新的 Range。这里有个坑in.seek()对 HDFS 来说不是免费的。如果 offset 落在第二个块上客户端需要重新跟那个块的 DataNode 建立连接。所以断点续传的粒度最好按块对齐或者至少不要频繁 seek。3.4 目录操作和权限校验别让用户看到别人的文件文件列表、创建目录、重命名、删除这几个操作都是对FileSystem的封装代码不复杂但权限校验必须做在服务层public static ListFileStatus listFiles(String userDir) throws Exception { FileSystem fs getFS(); Path dir new Path(userDir); if (!fs.exists(dir)) { return Collections.emptyList(); } FileStatus[] statuses fs.listStatus(dir); // 过滤掉隐藏文件以 . 或 _ 开头 return Arrays.stream(statuses) .filter(s - !s.getPath().getName().startsWith(.)) .filter(s - !s.getPath().getName().startsWith(_)) .collect(Collectors.toList()); }listStatus返回的数组里会包含 HDFS 自己产生的隐藏文件比如_SUCCESS不过滤的话前端会显示一堆莫名其妙的文件。权限校验的逻辑是从 session 里拿到当前用户 ID拼出/user/cloudpan/{userId}/作为根目录所有操作只能在这个前缀下进行。如果用户传的路径里包含..或者绝对路径跳转直接拒绝。4. 避坑排查那些让课程设计卡三天的真实问题4.1 上传大文件时 NameNode 报「Too many open files」现象上传一个 2GB 的文件传到一半报java.io.IOException: Too many open filesNameNode 日志里一堆LeaseExpiredException。原因HDFS 客户端每写一个块就要跟 NameNode 申请一次租约lease同时打开的 socket 和文件句柄数超过系统限制。Linux 默认的ulimit -n是 1024大文件分块多的时候不够用。解决调大文件句柄限制。临时生效用ulimit -n 65535永久生效改/etc/security/limits.conf加两行* soft nofile 65535和* hard nofile 65535重启后ulimit -n确认。另外在hdfs-site.xml里把dfs.namenode.fs-limits.max-blocks-per-file适当调大。4.2 伪分布式启动后 DataNode 起不来现象start-dfs.sh执行完jps只看到 NameNode没有 DataNode。DataNode 日志报java.net.BindException: Address already in use或者Incompatible clusterIDs。原因两种情况。一是之前格式化过多次dfs.namenode.name.dir和dfs.datanode.data.dir里的 clusterID 不一致二是 50010 端口被别的进程占了。解决先stop-dfs.sh然后删掉name和data两个目录下的所有内容重新hdfs namenode -format再启动。注意格式化只能做一次做多了 clusterID 必乱。端口占用用netstat -tlnp | grep 50010查找到进程 kill 掉。4.3 Windows 开发环境连不上 HDFS现象在 Windows 的 IDEA 里跑代码报Failed to locate the winutils binary in the hadoop binary path或者UnsatisfiedLinkError。原因Hadoop 的 native 库是为 Linux 编译的Windows 下缺少winutils.exe和hadoop.dll。解决下载对应 Hadoop 版本的 winutils 包放到HADOOP_HOME/bin下并在系统环境变量里设HADOOP_HOME指向那个目录。更省事的做法是开发在 Windows、运行在 Linux 虚拟机用 Maven 打包后scp到虚拟机跑。我一般推荐后者少折腾环境。4.4 文件上传成功但列表里看不到现象hdfs dfs -ls能看到文件但 Web 页面刷新后列表是空的。原因元数据表没写入或者写入的hdfs_path和实际路径大小写、斜杠不一致。HDFS 路径是大小写敏感的/user/CloudPan/和/user/cloudpan/是两个目录。解决上传成功后立即写元数据并且把路径统一转成小写、去掉末尾斜杠再存。查询时先用元数据表的路径去fs.exists()确认不存在就标记为失效并记录日志。另外检查一下listStatus的过滤条件是不是把正常文件也过滤掉了。4.5 删除文件后空间没释放现象删了一批文件hdfs dfs -du -h /显示占用没变。原因HDFS 的delete()默认是永久删除但如果你的代码调的是trash相关 API文件会进回收站/user/{user}/.Trash/保留周期默认 7 天。另外DataNode 的块删除是异步的NameNode 标记删除后要等 DataNode 下次心跳才真正删。解决确认删除用的是fs.delete(path, true)而不是移到回收站。如果确实要立即释放调fs.truncate()或者等 DataNode 心跳周期默认 3 秒过后再看。课程设计演示时提前删好别现场删大文件然后等半天。5. 让项目从「能跑」到「能答辩」的几个进阶技巧5.1 用 Docker 把 Hadoop 环境固化下来每次换电脑都要重装一遍 Hadoop 太痛苦用 Docker 可以一键拉起。社区有现成的 Hadoop 镜像但版本参差不齐我一般自己写一个简单的 DockerfileFROM openjdk:8-jdk-slim RUN apt update apt install -y ssh pdsh \ ssh-keygen -t rsa -P -f ~/.ssh/id_rsa \ cat ~/.ssh/id_rsa.pub ~/.ssh/authorized_keys COPY hadoop-3.3.6.tar.gz /opt/ RUN tar -xzf /opt/hadoop-3.3.6.tar.gz -C /opt/ \ mv /opt/hadoop-3.3.6 /opt/hadoop ENV HADOOP_HOME/opt/hadoop ENV PATH$PATH:$HADOOP_HOME/bin:$HADOOP_HOME/sbin COPY core-site.xml hdfs-site.xml /opt/hadoop/etc/hadoop/ RUN hdfs namenode -format EXPOSE 9000 9870 50010 CMD [start-dfs.sh]构建后docker run -d -p 9000:9000 -p 9870:9870就能用。注意hdfs namenode -format放在镜像构建阶段容器重启不会重复格式化。这个做法在答辩时演示环境迁移特别方便评委问「换台机器能跑吗」你直接docker run给他看。5.2 用单元测试证明你的 HDFS 操作是对的课程设计代码最怕评委问「你怎么保证上传的文件没损坏」。写几个 JUnit 测试用 MD5 校验Test public void testUploadIntegrity() throws Exception { String localFile /tmp/test_10mb.bin; String hdfsPath /user/cloudpan/test/test_10mb.bin; // 生成本地测试文件 byte[] data new byte[10 * 1024 * 1024]; new Random().nextBytes(data); Files.write(Paths.get(localFile), data); HdfsUtil.upload(localFile, hdfsPath); // 下载回来比对 MD5 String localMd5 DigestUtils.md5Hex(new FileInputStream(localFile)); String remoteMd5 HdfsUtil.getMd5(hdfsPath); assertEquals(localMd5, remoteMd5); }getMd5的实现是读 HDFS 流算摘要。这个测试跑通答辩时直接展示测试报告比口头说「我测过了」有说服力得多。测试文件大小建议覆盖 1MB、10MB、200MB 三档分别验证小文件、中等文件、跨块文件的完整性。5.3 文档说明该写什么才不会被扣分很多人的文档说明就是 README 里贴几行命令这不够。一份能拿高分的文档至少包含环境依赖清单JDK 版本、Hadoop 版本、MySQL 版本、Maven 依赖、部署步骤从装系统到启动服务的完整命令、功能清单每个功能对应哪个类哪个方法、数据库表结构建表 SQL 加字段说明、测试用例输入什么、预期输出什么、实际结果、已知问题哪些场景没覆盖、为什么。特别是「已知问题」这一节主动暴露边界比藏着掖着更显专业。比如你可以写「当前版本不支持文件夹的递归删除删除非空目录会报错后续版本计划用fs.delete(path, true)修复」。5.4 答辩时被问到 HDFS 和传统文件系统的区别怎么答这个问题几乎每场答辩都会出现。我的答法是抓三个点第一HDFS 的块比传统文件系统大得多128MB vs 4KB目的是减少寻址开销适合一次写入多次读取的大文件场景第二HDFS 的副本机制让数据可靠性不依赖单块磁盘但代价是存储空间利用率降到三分之一第三HDFS 不支持随机写只能追加append或者覆盖所以它不适合做数据库适合做数据湖的底层存储。数据云盘项目正好卡在「适合」这一侧——用户上传后很少修改主要是读和下载。最后说个我自己的习惯每次做完一个 HDFS 项目我都会把hdfs dfsadmin -report的输出存一份记录集群的容量、块数、副本状态。下次再遇到「上传慢」「空间不够」这类问题先拿这份基线数据对比能省掉一半的排查时间。希望帮到你。本文还有配套的精品资源点击获取
返回列表