简介:基于Hadoop的百度云盘项目,是一套面向大数据与Java Web开发学习者的完整毕设源码包,涵盖分布式存储后端与网盘前端管理界面。资源共包含2000个文件,压缩包大小约77.11MB,其中Java源码、JSP页面、配置文件与数据库文件支撑核心业务逻辑,854张PNG图片及CSS、JS、HTML文件构建完整界面与交互,另附jar依赖库便于直接部署调试。项目代码已全部运行通过,适合高校计算机相关专业学生作为毕业设计、课程设计或项目初期演示;配套文档说明可帮助理解Hadoop集成、文件上传下载、用户管理等模块实现思路,资源目录结构清晰,便于按功能模块检索和二次开发。目前已有535人学习下载,下载后还可私信沟通,支持远程教学指导。
1. 基于Hadoop的百度云盘:课程设计与毕业设计里最值得复现的一个全家桶
当一个课程设计或毕业设计题目是「基于Hadoop的百度云盘」时,多数人第一反应是「写个网页,后端文件传到 HDFS 不就行了」。真上手才会发现,这个标题真正考验的是 HDFS 的写入方式、分块上传和文件合并的时序、元数据与文件内容的一致性,以及文档里给的部署步骤能不能在别人的机器上复现。我拆过几套类似的项目源代码,结论是一致的:这类项目代码量不大,但踩坑密度很高。本文按「架构拆解 → 伪分布式存储底座 → 源代码阅读 → 避坑 → 集群迁移」的顺序,把你打开这个标题后最想搞清楚的事一次讲明白,不绕弯。适合要交课设或毕设的学生,也想借网盘业务快速验证 HDFS 能力的开发。
2. 基于Hadoop的百度云盘架构拆解:HDFS、元数据库与上传链路各管哪一段
2.1 网盘项目里两种存储怎么分工:内容进HDFS,状态进MySQL
很多人把「基于 Hadoop 的百度云盘」想成一个黑匣子:前端传文件,后端往 HDFS 里一丢就结束。实际上,HDFS 在这个项目里只负责一件事——文件内容本身。文件名、文件大小、所在目录、上传状态、分块编号这些信息,如果也塞进 HDFS,会带来两个后果:一是小文件数量爆炸,NameNode 内存被撑满;二是每次列目录都要去访问 HDFS,响应延迟到无法接受。
我一般会这样划分:文件字节流进 HDFS,业务元数据进 MySQL。对应到代码里,MySQL 的表至少要有用户表、文件记录表和分享表。文件记录表是核心,字段大致这样设计:
CREATE TABLE file_record ( id BIGINT PRIMARY KEY AUTO_INCREMENT, user_id BIGINT NOT NULL, file_name VARCHAR(255) NOT NULL, hdfs_path VARCHAR(500) NOT NULL, file_size BIGINT DEFAULT 0, chunk_count INT DEFAULT 1, upload_status TINYINT DEFAULT 0, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, KEY idx_user (user_id, created_at) );upload_status 用 0 表示上传中、1 表示成功、2 表示失败。这个字段在断点续传和合并校验时非常关键,后面避坑章节会专门说。
HDFS 侧的文件路径建议按「用户 + 日期 + 临时/最终」分层,比如:
/netdisk/user_1001/20240520/final/ 合并完成的文件 /netdisk/user_1001/20240520/tmp/ 尚未合并的分块分块文件放在 tmp 目录而不是 final 目录,是因为合并动作还没完成之前,不能让用户能访问到半成品。前端展示的目录树和文件列表,全部走后端查 MySQL,再用 hdfs_path 去 HDFS 做流式读取。这一层边界想清楚了,后面写代码才不会把 HDFS 当成数据库用。
2.2 上传一个100MB文件,后端到底对HDFS做了什么
伪装分布式的 Hadoop 上,单副本存储一个 100MB 文件本身没有问题,HDFS 默认 block 是 128MB,一个文件一个 block 就放下了。但真实网盘场景里前端不能把整个 100MB 一次性 POST 给后端,一是 HTTP 连接超时风险高,二是失败重传成本太大。所以完整的链路是「前端分块 → 后端逐块落 HDFS → 全部到位后合并」。
前端可以用 Web 的 File.slice() 把文件切成 4MB 或 8MB 的分块,每个分块带一个序号请求后端。后端拿到分块后,不直接写最终路径,而是写入 tmp 目录下的独立文件。等前端通知「所有分块上传完毕」,后端再新建一个 FSDataOutputStream 写入 final 路径,按分块序号依次把 tmp 文件的内容拷贝进去。
public String completeUpload(FileRecord record, String tmpDir, String finalPath) { Configuration conf = new Configuration(); conf.set("fs.defaultFS", "hdfs://node01:9000"); try (FileSystem fs = FileSystem.get(conf)) { // 按分块序号排序,这一点非常关键 List<Path> parts = new ArrayList<>(); for (int i = 1; i <= record.getChunkCount(); i++) { parts.add(new Path(tmpDir + "/chunk_" + i)); } try (FSDataOutputStream out = fs.create(new Path(finalPath), true)) { for (Path part : parts) { try (FSDataInputStream in = fs.open(part)) { IOUtils.copyBytes(in, out, 8192, false); } } } // 合并后删除临时分块,避免小文件堆积 for (Path part : parts) { fs.delete(part, false); } return finalPath; } catch (IOException e) { throw new RuntimeException("HDFS合并失败: " + e.getMessage(), e); } }这个代码里有两个关键点。第一,fs.create(finalPath, true)的第二个参数表示覆盖写,如果同名文件已经存在会被覆盖,所以调用前一定要确认这个 finalPath 在业务上是否允许重复。第二,IOUtils.copyBytes(in, out, 8192, false)的最后一个参数是关闭流,这里必须传 false,因为 out 是多个分块共用的流,不能第一个分块拷完就把输出流关了。
分块大小的选择上,局域网内 4MB 和 8MB 差别不大,跨公网部署建议 8MB 起步。太小会导致分块数量大、每块的元数据传输开销被放大;太大又会回到「一个请求传很久」的老问题。
2.3 用 FileSystem API 还是 WebHDFS:三个决定参数
后端连 HDFS 有两条主流路径:原生 FileSystem API 和 WebHDFS REST API。一条条对比很占篇幅,我直接说结论。项目里的后端如果和 Hadoop 集群在同一个内网,甚至就部署在 NameNode 同一台机器上,用 FileSystem API,因为延迟低、写代码直观,能直接拿到输入输出流。后端和集群跨网络部署且不方便开 RPC 端口时,用 WebHDFS,HTTP 协议穿透性好,但每次读写多一层 JSON 解析和连接开销。
| 场景 | 推荐方案 | 理由 |
|---|---|---|
| 课设/毕设,后端和Hadoop同机 | FileSystem API | 配置简单,调试方便 |
| 后端在独立服务器 | WebHDFS | 只需开 HTTP 端口 |
| 跨公网传输 | WebHDFS + 业务层限速 | 避免集群端口暴露过广 |
用 FileSystem API 时,core-site.xml 里的fs.defaultFS要和代码里 conf.set 的地址一致,否则会出现「连接不上文件系统」的经典报错。我在 HdfsConfig 配置类里统一维护这两个值,不让它在代码里散落。
3. 从零开始搭建Hadoop伪分布式存储底座:安装、配置与启动验证全记录
3.1 Hadoop环境准备:JDK版本、SSH免密与hosts映射三条硬规矩
基于 Hadoop 的百度云盘项目,存储底座跑的是 HDFS,而 HDFS 的 NameNode 和 DataNode 启动依赖 SSH 免密登录。伪分布式安装虽然是单机,依然要遵守这套规矩,否则 start-dfs.sh 会在执行到远端启动脚本时卡住。
环境准备我按这个顺序做,前后有依赖关系:
# 1. 安装 JDK8 并配置环境变量 sudo tar -zxvf jdk-8u202-linux-x64.tar.gz -C /opt/ sudo update-alternatives --install /usr/bin/java java /opt/jdk1.8.0_202/bin/java 100 export JAVA_HOME=/opt/jdk1.8.0_202 export PATH=$JAVA_HOME/bin:$PATH # 2. 生成SSH密钥并配置单机免密 ssh-keygen -t rsa -P '' -f ~/.ssh/id_rsa cat ~/.ssh/id_rsa.pub >> ~/.ssh/authorized_keys chmod 600 ~/.ssh/authorized_keys # 3. 测试免密 ssh localhostJDK 版本上,Hadoop 2.x 用 JDK8,Hadoop 3.3.x 兼容 JDK8,不要用 JDK17 跑老项目,否则反射相关的警告会刷屏。SSH 密钥生成后记得用 ssh localhost 实际验证一次,首次会提示确认指纹,输入 yes 后才算真正建立信任。
hosts 映射这一步容易被忽略。Hadoop 启动脚本会解析主机名,我习惯在 /etc/hosts 里写一行:
192.168.1.10 node01同时把 HADOOP_HOME 写进 /etc/profile,避免每次手动 export。
3.2 Hadoop核心文件修改:core-site.xml与hdfs-site.xml的必调参数
每台机器的 Hadoop 配置都集中在 etc/hadoop 目录下。伪分布式要改两个文件:core-site.xml 和 hdfs-site.xml。改之前在 conf 里留一行注释写「伪分布式单机」,以后迁移集群对照时能少走弯路。
core-site.xml 关键参数:
<configuration> <property> <name>fs.defaultFS</name> <value>hdfs://node01:9000</value> </property> <property> <name>hadoop.tmp.dir</name> <value>/home/hadoop/data/hadoop_tmp</value> </property> </configuration>fs.defaultFS 声明了默认文件系统地址,后端代码里使用 hdfs://node01:9000 就是这里对上的。hadoop.tmp.dir 是 NameNode 和 DataNode 存放元数据与数据块的根目录,这个路径的磁盘空间要提前规划,网盘项目测试期至少预留 50GB。
hdfs-site.xml 在伪分布式下的必调参数:
<configuration> <property> <name>dfs.replication</name> <value>1</value> </property> <property> <name>dfs.namenode.name.dir</name> <value>file:///home/hadoop/data/namenode</value> </property> <property> <name>dfs.datanode.data.dir</name> <value>file:///home/hadoop/data/datanode</value> </property> </configuration>dfs.replication 在伪分布式下必须设置成 1,因为只有一台 DataNode,默认副本数 3 会导致所有 block 都进入等待副本的状态,上传成功但文件始终显示 under replication。namenode.name.dir 和 datanode.data.dir 最好显式指定,不要依赖默认的临时目录,因为 /tmp 在重启后可能被系统清理。
3.3 格式化与启动:NameNode与DataNode的启动顺序和验证方法
配置改完后,第一步是格式化 NameNode,顺序不要反。先格式化再启动,格式化会生成当前集群的 namespace 信息,后面启动脚本读的就是这部分。
# 在 HADOOP_HOME 下执行 hdfs namenode -format # 启动 HDFS start-dfs.sh # 查看进程 jps格式化时如果显示「Re-format filesystem」字样,说明之前已经格式化过,此时要谨慎,因为它会清空原 NameNode 上的元数据,等于给整个集群吃了后悔药,但药效是回到原点。jps 输出应该看到 NameNode、DataNode、SecondaryNameNode 三个进程,少任何一个都先看日志,日志在 $HADOOP_HOME/logs 下,不要凭感觉重启。
验证伪分布式是否正常,顺手做两步:
# 创建测试目录并上传文件 hdfs dfs -mkdir -p /netdisk/test hdfs dfs -put /etc/profile /netdisk/test/profile_test # 查看数据块信息 hdfs fsck /netdisk/test/profile_test -files -blocksfsck 能直接看到文件在哪个 DataNode 上、副本数是否达标。这一步跑通,说明存储底座已经能供网盘后端使用了。
4. 源代码结构拆解:从控制器到HDFS客户端,一段可抄的上传实现
4.1 源代码目录怎么看:controller、service、hdfs三层的真身
拿到基于 Hadoop 的百度云盘源代码,先别急着跑,按照 Spring Boot 的常规分层找到三个入口:控制器、业务服务、HDFS 操作类。多数项目源码结构类似:
netdisk/ ├── src/main/java/com/netdisk/ │ ├── controller/ │ │ ├── FileController.java │ │ └── UserController.java │ ├── service/ │ │ ├── FileService.java │ │ └── UserService.java │ ├── hdfs/ │ │ ├── HdfsConfig.java │ │ └── HdfsFileService.java │ └── entity/ │ └── FileRecord.java ├── src/main/resources/ │ ├── application.yml │ ├── mapper/ │ └── sql/init.sql └── pom.xmlHdfsConfig.java 是理解这个项目的第一把钥匙。它负责构建 Configuration 对象和 FileSystem 实例,里面通常会有 fs.defaultFS 的地址、HDFS 用户、连接池相关的设置。HdfsFileService 是操作 HDFS 的唯一入口,里面应该只有 upload、download、delete、list 四种基础能力,任何 controller 不能直接 new Path 去访问 HDFS,否则代码会失控。
4.2 用Hadoop客户端上传文件的Java核心代码:参数与调用逻辑
FileController 接收 MultipartFile,转交给 FileService,FileService 再调 HdfsFileService。这个调用链上的每一步都有实际意义:controller 只做参数校验,service 负责事务和元数据落库,HdfsFileService 只管字节流。下面这段是 HdfsFileService 上传的核心方法:
@Service public class HdfsFileService { @Autowired private HdfsConfig hdfsConfig; public String upload(InputStream in, String hdfsPath) throws IOException { Configuration conf = hdfsConfig.getConfiguration(); try (FileSystem fs = FileSystem.get(conf)) { Path path = new Path(hdfsPath); try (FSDataOutputStream out = fs.create(path, true)) { IOUtils.copyBytes(in, out, 8192, true); } } return hdfsPath; } }注意这里的fs.create(path, true)与分块合并时一样是覆盖写。一个小文件直接上传时,文件路径通常是 /netdisk/user_1001/20240520/final/xxx.pdf,这个名字如果和已上传文件重名,会静默覆盖旧文件。我在 FileService 里做了同名检测,文件名冲突时自动追加时间戳后缀。
调用这段上传前还需要一个前置步骤:确认父目录存在。HDFS 的 create 不会自动创建多级父目录,需要先调用fs.mkdirs(new Path("/netdisk/user_" + userId)),否则第一次上传必报 Parent directory not found。
4.3 文档说明里最该写清楚的三个部分:环境清单、初始化SQL、部署步骤
标题带了「文档说明」,可很多项目的 README 只写了一句「基于 Hadoop 的云盘系统」。我见过的文档里,对读者最有价值的三个部分是:环境清单、初始化 SQL、部署步骤。
环境清单要精确到版本,比如 JDK 8u202、Hadoop 3.3.4、MySQL 5.7、Maven 3.6。这里最坑的是 Hadoop 版本和 JDK 版本的组合,写清楚可以省去读者半天的环境排查时间。
初始化 SQL 不能只给建表语句,还要包含测试用户和测试目录的 INSERT。没有测试数据,前端登录后看不到任何文件,很容易误判是项目问题。
部署步骤要写清顺序:先配置 Hadoop 并启动 HDFS,再执行 init.sql 初始化数据库,然后修改 application.yml 里的 HDFS 地址和 MySQL 地址,最后 package 启动。每一步配上验证命令,比大段原理说明有用得多。
5. 网盘项目避坑指南:5个让源代码跑不起来的Hadoop存储坑
5.1 上传文件报「块分配失败」,但DataNode明明启动了
现象:前端上传文件,后端日志打印java.io.IOException: All datanodes are bad,但 jps 里 DataNode 进程确实在。
原因:最常见的两种情况。一是 dfs.replication 大于实际 DataNode 数量,伪分布式里设了默认副本 3,而机器只有一台 DataNode,Block 无法分配给这么多副本。二是 DataNode 所在磁盘剩余空间不足或处于 safemode。
解决:先执行hdfs dfsadmin -report查看 DataNode 的可用容量,再确认 hdfs-site.xml 中 replication 是否为 1。如果是安全模式,执行hdfs dfsadmin -safemode leave。我遇到过的项目中,八成问题出在 replication 配置,两成是磁盘写满。
5.2 分块合并后文件打开损坏,或合并顺序错乱
现象:断点续传功能在测试时表现正常,真传一个 200MB 的文件后下载下来,压缩包解压报错,PDF 渲染一半。
原因:合并逻辑按 HashMap 遍历分块,或者按文件名长度排序而不是按数字序号排序,导致 chunk_10 排在 chunk_2 前面。HDFS 文件名排序是按字典序,chunk_10 天然排在 chunk_2 前,必须将文件名里的数字提取出来做整数排序。
解决:合并前对分块列表执行parts.sort(Comparator.comparingInt(p -> extractChunkIndex(p))),并建议合并时校验每个分块的实际字节数是否与前端上报一致,不一致直接标记该文件上传失败而不是生成坏文件。
5.3 Windows上用IDEA连不上Hadoop,代码里conf.set写了localhost也没用
现象:后端在 Windows 本地启动,Hadoop 跑在虚拟机或远程服务器,上传时卡住直到超时。
原因:Hadoop 的 FileSystem 客户端启动后,会拿本机的主机名去反向解析,Windows 机上如果 hosts 里没有对应映射,会默认解析到 127.0.0.1,数据流就断了。
解决:Windows 的 C:\Windows\System32\drivers\etc\hosts 里加上 Hadoop 服务器的 ip 和主机名映射,再把 application.yml 里的 hdfs 地址改成服务器内网 IP,而不是在代码里写死 localhost。这条坑在部署到真实集群时同样适用,NameNode RPC 地址千万不要写成 127.0.0.1。
5.4 NameNode内存暴涨,上传大量小文件后Web UI卡死
现象:网盘上传功能正常,但用脚本批量上传几百个小文件后,NameNode 所在的机器负载升高,Web UI 打开变慢。
原因:HDFS 里每个文件、每个 block 都会在 NameNode 内存中建立对象,小文件数量一多,内存占用线性增长。网盘项目如果把每个分块都保留在 HDFS 不清理,就等于在制造小文件灾难。
解决:上传完成后及时清理 tmp 目录下的分块;业务层限制单次上传分块数量,超过 100 个分块的触发异步合并。这是文件系统层面的硬约束,后端写得再漂亮也绕不过去。
5.5 重新格式化HDFS后,网盘里的文件全部消失
现象:Hadoop 集群运行一段时间后出了问题,网上教程让重新执行 hdfs namenode -format,格式化后启动成功,但网盘里所有文件都没了,DataNode 报多个 block 无法找到。
原因:NameNode 格式化会生成新的集群 ID,原有 DataNode 上的数据块还在旧集群 ID 下,新 NameNode 不认识它们。DataNode 启动后发现集群 ID 不匹配,会拒绝加载旧 block。
解决:如果只是 NameNode 元数据损坏,优先用 fsimage 目录里的备份恢复,而不是格式化。实在要重建集群,格式化前把 /home/hadoop/data/datanode/current/VERSION 里的 clusterID 记录下来,格式化后改回原值再启动 DataNode。我在文档说明里都会把这步单独写进部署注意事项,因为格式化真的是最后手段。
6. 把伪分布式方案改造成三节点集群:一个可执行的迁移与验证路径
6.1 从单机到三节点的改动清单
网盘项目在伪分布式上验证完功能,下一步往往是租三台云服务器搭小集群。配置文件有五个地方要动:
| 节点 | 角色 | 配置变化 |
|---|---|---|
| node01 | NameNode + SecondaryNameNode | fs.defaultFS 改为 hdfs://node01:9000 |
| node02 | DataNode | 仅修改 dfs.datanode.data.dir |
| node03 | DataNode | 仅修改 dfs.datanode.data.dir |
core-site.xml 在三个节点保持一致,hdfs-site.xml 里 dfs.replication 从 1 改成 2,保证单节点宕机不丢数据。启动顺序改为先在 node01 执行 start-dfs.sh,然后分别在 node02、node03 执行 hdfs --daemon start datanode。
6.2 用Python脚本验证网盘接口在集群上的并发能力
集群搭好后,随便传一个文件不能说明问题。我用一段 Python 脚本做并发上传,确认接口在真实并发下不会把 NameNode 打爆:
import requests from concurrent.futures import ThreadPoolExecutor # 模拟20个用户同时上传分块 def upload_chunk(user_id, chunk_id): payload = b"test content" * 4096 resp = requests.post( "http://node01:8080/api/upload", data={"userId": user_id, "chunkId": chunk_id}, files={"file": payload}, timeout=10 ) return resp.status_code with ThreadPoolExecutor(max_workers=20) as executor: results = list(executor.map( lambda x: upload_chunk(x // 10, x % 10), range(200) )) print("success:", results.count(200), "failed:", results.count(500))这个脚本的并行度不要一开始就拉满,我习惯先 20 并发跑一轮,观察 NameNode 日志里有没有超时,再逐步加到 50。并发数的参考值不是越高越好,伪分布式集群和真实集群的承载能力相差很大。
6.3 最后的检查习惯
迁移完成后的第一件事不是打开前端页面,而是执行 hdfs dfsadmin -report,确认两个 DataNode 都活着、剩余空间足够。然后再跑一遍上传下载闭环,最后看 NameNode 的 Web UI 上是否有 under-replicated block 告警。这个顺序我用了很久,它能省掉大半「前端报错其实是后端存储出问题」的排查时间。希望帮到你。
本文还有配套的精品资源,点击获取