简介:一份基于Hadoop的百度云盘系统完整源码与配套文档,面向大数据相关专业的在校学生、毕业设计工作者以及希望上手分布式存储的初学者。压缩包共包含2000个文件,整体容量77.11MB,文件类型以Java后端源码、JSP动态页面、前端网页样式与交互脚本、项目配置文件及依赖库为主,前后端层次清晰。所有核心代码均经过运行验证,功能可用,并附带说明文档,可帮助读者梳理Hadoop分布式文件系统与Web应用结合的设计思路,理解文件上传下载、元数据管理等关键环节。包内还提供了大量页面展示图片和界面样式资源,便于对照效果或进行二次开发改进。目前已有535人学习下载,适合作为毕业设计参考案例、课程设计作业或大数据入门的实践项目。
1. 基于Hadoop的百度云盘到底是个什么项目:先看它解决了什么
很多人第一次看到“基于Hadoop的百度云盘”这个标题,第一反应是“用Hadoop做一个百度云盘网页版”?对,也不对。它本质是一个把 HDFS(Hadoop 分布式文件系统)当成后端存储的网盘项目,前端用 Bootstrap + EasyUI 做管理界面,用户能注册、登录、上传、下载、建目录、删文件,但底层存数据的不是本地磁盘,而是 HDFS 的数据节点。你上传一个文件,它会被切块复制到多个节点,这正好是大数据课设里最容易被答辩老师追问的分布式存储场景。
这类项目适合三类人:一是计算机相关专业做毕设或课设的学生,想拿 HDFS 实操来证明“我懂大数据”;二是刚学完 Hadoop 想找个完整工程练手的 Java 开发者,能从 Web 层一路看到分布式存储调用;三是想快速搭一套内部文件管理演示系统的工程师——当然生产环境另说。源码里已经整合了登录鉴权、文件操作、目录树,大部分功能是跑通的,下载后重点不是改代码,而是配合文档把环境和参数吃透。下面我从架构讲起,再给部署步骤、二次开发思路和踩坑记录,最后附一个我常用的 HDFS 数据迁移技巧。
2. 核心架构与数据流:HDFS 在网盘里扮演什么角色
2.1 整体技术栈:Java Web + EasyUI 前端 + HDFS 客户端
打开项目源码,你能看到ace.min.css、bootstrap.min.css、easyui.css这些前端资源,说明界面并不是现在流行的 Vue/React,而是基于 EasyUI 这套老牌后台框架。后端几乎可以肯定是 Java Web(Servlet 或 Spring MVC),Hadoop 官方提供的 Java API 直接封装成 Service 层。这种组合在课程设计里非常标准:EasyUI 负责后台布局和表格渲染,Bootstrap 做页面美化,HDFS 客户端负责跟 NameNode 和 DataNode 通信。
这种选型在今天看起来不是“最潮”的,但它有一个非常大的优势:链路短。前端发起请求 → Controller 接收参数 → Service 调用FileSystemAPI → HDFS 返回结果。中间没有 Kafka、没有微服务,任何一个环节出问题,用日志就能快速定位。对毕设而言,答辩老师最关心的是“你怎么调用 HDFS 的 API?上传文件的副本因子是多少?数据块多大?”,这些恰恰是这个项目最实在的考点。
2.2 文件上传下载:一条完整的 HDFS 读写链路
网盘的核心功能就是文件传输。在基于 HDFS 的云盘里,上传逻辑一般是这样:
// 伪代码,摘自项目中常见实现(非完整源码) Configuration conf = new Configuration(); conf.set("fs.defaultFS", "hdfs://localhost:9000"); FileSystem fs = FileSystem.get(conf); Path remotePath = new Path("/user/file/" + filename); FSDataOutputStream out = fs.create(remotePath, true); byte[] buffer = new byte[1024 * 1024]; int len = -1; while ((len = in.read(buffer)) != -1) { out.write(buffer, 0, len); } out.close(); fs.close();这段代码里,fs.defaultFS指定的是 HDFS 的 NameNode 地址;fs.create会拿第三个参数当副本因子(默认 3);写入流会先写进 DataNode 的本地磁盘,再通过 pipeline 复制到其他节点。很多人第一次跑项目看到上传慢,以为是代码问题,其实是你本地 Hadoop 是伪分布式模式,只有一个 DataNode,副本因子配成 3 它也存不了三份,只会报异常或者默默降级。
下载逻辑就反过来:
FSDataInputStream in = fs.open(remotePath); IOUtils.copyBytes(in, response.getOutputStream(), conf);这里有个容易被忽视的坑:response是 HTTP 响应对象,如果你在构造下载时设置了Content-Disposition文件名,必须对中文名做 URL 编码,否则前端拿到的文件名是乱码。源码里如果没做编码,你下载中文文件名时大概率会遇到这个问题。
2.3 元数据怎么设计:目录树与文件表的取舍
HDFS 本身没有“文件夹”的概念,Path只是逻辑路径。所以网盘系统要自己建一张元数据表,记录每个用户的目录结构、文件名、大小、上传时间、HDFS 路径。常见的做法有两种:
- 把用户和路径直接映射到 HDFS 目录:
/user/zhangsan/,登录后只加载这个前缀下的文件。这种最简单,删除目录就是fs.delete递归删。 - 用 MySQL 存元数据,HDFS 只存文件块。推荐项目里用这种方式,因为后续做用户配额、回收站、分享链接都在 MySQL 里做更省事。
我一般会这样设计表字段:id、user_id、parent_id、file_name、hdfs_path、file_size、file_type、create_time、is_delete。注意is_delete不是真的去调fs.delete,而是把记录标记为删除,这样回收站功能好实现。源码里的文档说明如果没提表结构,你可以看doc目录下的 SQL 文件,里面通常会带建表语句。
3. 把项目跑起来:环境准备、参数配置与部署顺序
3.1 Hadoop 伪分布式环境搭建:三个关键配置
不管代码写得多好,第一步永远是先把 Hadoop 跑起来。推荐用 Hadoop 2.x/3.x 的伪分布式模式,内存要求不高,一般 8G 内存的笔记本就能带起来。核心配置文件就三个:
core-site.xml:
<property> <name>fs.defaultFS</name> <value>hdfs://localhost:9000</value> </property> <property> <name>hadoop.tmp.dir</name> <value>/home/hadoop/tmp</value> </property>hdfs-site.xml:
<property> <name>dfs.replication</name> <value>1</value> </property> <property> <name>dfs.namenode.name.dir</name> <value>/home/hadoop/name</value> </property> <property> <name>dfs.datanode.data.dir</name> <value>/home/hadoop/data</value> </property>mapred-site.xml或yarn-site.xml按默认配置即可,如果只是跑网盘,MapReduce 其实用不到,但 Hadoop 进程需要 YARN 正常启动才不会报协议版本错误。
特别注意hadoop.tmp.dir和dfs.namenode.name.dir不能放在/tmp下,否则系统重启清空/tmp后,你的 NameNode 格式化数据就没了。这是 Hadoop 新人第一大坑。
启动顺序是:先hdfs namenode -format初始化(只有第一次需要),然后start-dfs.sh,再用jps确认有NameNode、DataNode、SecondaryNameNode三个进程。如果没有DataNode,多半是dfs.datanode.data.dir目录不存在或没有写权限,mkdir -p建目录再重启即可。
3.2 源码导入与配置项修改:别踩端口冲突
项目源码一般是 Eclipse/Maven 工程,.classpath文件本身就是 Eclipse 格式,你可以直接用 Eclipse 导入。如果是 IDEA,选 import project from existing sources,选 Maven 模型,等依赖下载完。
需要修改的地方集中在项目里的hadoop.properties或application.xml配置文件:
hdfs.defaultFs=hdfs://localhost:9000 hdfs.username=hadoop hdfs.homeDir=/user hdfs.replication=1hdfs.username很关键。HDFS 有权限校验,如果你用 root 启动 Hadoop,而 Web 项目进程是 tomcat,在/user下建目录时会被Permission denied挡住。最省事的做法是在hdfs-site.xml里临时加一条:
<property> <name>dfs.permissions.enabled</name> <value>false</value> </property>这是伪分布式环境下的开发取巧方式,生产环境绝对不能这么干。等你跑通了,再考虑用Kerberos或ranger做真正权限控制。
另外,Hadoop 默认占用的端口比较多,最常见的冲突是 8088(YARN Web UI)和你 Tomcat 的 8080 没关系,但 50070(HDFS Web UI)会被某些监控软件占用。改端口在hdfs-site.xml里加dfs.namenode.http-address,或者干脆把占用端口的进程停掉。
3.3 部署顺序与验收清单
给第一次跑项目的人一个顺序参考:
- 确认 JDK 版本(1.8 最稳,Hadoop 3.x 需要 1.8+)。
- 配置 SSH localhost 免密登录,否则 start-dfs.sh 会要求输入密码。
- 格式化 NameNode,启动 DFS。
- 建好 HDFS 根目录:
hdfs dfs -mkdir -p /user。 - 初始化 MySQL 表,导入项目里带的 SQL 文件。
- 启动 Tomcat,访问登录页。
- 注册一个新用户,上传一个文件,再下载回来对比 MD5。
验收的时候别只看页面显示成功。到 HDFS 里看看实际存储:
hdfs dfs -ls /user/zhangsan hdfs dfs -du -h /user/zhangsan如果能看到文件且大小一致,才说明 Web 层到 HDFS 的链路真的通了。很多项目页面报成功,其实只是把文件存到了 Tomcat 临时目录,根本没走 HDFS,这种假成功比失败更坑。
4. 二次开发实战:给云盘加上用户配额、分片上传和回收站
4.1 用户目录隔离与空间配额
源码自带的用户体系一般比较简单,登录后所有用户共用/user根目录。我建议改成每个用户一个独立目录,并加上配额限制,这样更像真实网盘。
思路是:注册时在 HDFS 上为每个用户创建目录:
Path userPath = new Path("/user/username_" + userId); if (!fs.exists(userPath)) { fs.mkdirs(userPath); }配额既可以用 HDFS 原生的配额命令,也可以在元数据表里加一个quota字段,每次上传时算一下当前用户已用空间:
SELECT SUM(file_size) FROM t_file WHERE user_id = ? AND is_delete = 0然后跟配额比对。HDFS 原生配额有个坑:hdfs dfs -setQuota只能限制文件数量,不能限制空间大小(setSpaceQuota可以,但是对目录递归生效,修改粒度粗)。所以我一般推荐在应用层做配额,简单直接,答辩时也好解释。
4.2 分片上传与断点续传:稳定性的关键改造
HDFS 对超大单文件处理没问题,但 Web 上传受限于 Tomcat 的maxPostSize和网络中断风险。网盘项目如果不做分片,超过 2GB 的文件基本传不上去。改造方案如下:
- 前端用 JavaScript 把文件切片(每片 8MB)。
- 后端每接收一片就追加写入 HDFS 的同一个文件。
- 记录已上传分片索引,下次续传时跳过已存在的分片。
HDFS 追加写的核心 API 是FSDataOutputStream的append方法,但要注意 HDFS 默认不支持对文件任意位置追加,只支持在文件末尾追加。所以分片顺序不能乱,必须保证严格按编号写入。代码示意:
FSDataOutputStream out; if (!fs.exists(filePath)) { out = fs.create(filePath); } else { out = fs.append(filePath); } out.write(bytes); out.hflush(); // 重要:不 flush 缓存,后续分片可能未落盘 out.close();参数说明:append模式需要在hdfs-site.xml里打开dfs.support.append(Hadoop 2.6+ 默认开启);hflush是把客户端缓冲区数据刷到 DataNode,但不等所有副本确认,适合连续追加;如果你要严格保证数据完整,可以调out.flush(),性能会差一点。
这个改造做完,上传大文件基本不会中断。断点续传只需要前端把已上传分片序号传给后端,后端在写文件前跳过对应的长度偏移——不过 HDFS 的append不支持从中间插入,所以我们的做法是“已上传的分片如果没写进同一个文件,就重传那片”。效率低一点,但逻辑简单可靠。
4.3 回收站实现:把删除变成软删除
真正的百度云盘删除文件后会进回收站,30 天内可恢复。HDFS 自身有一个.Trash机制,开启后fs.delete会转移到用户目录下.Trash中。但这对网盘业务不够灵活,因为要跟用户界面交互。我的做法是:
- 删除时只更新
t_file表is_delete = 1,HDFS 文件不动。 - 回收站页列出所有
is_delete = 1且delete_time > 30天的记录。 - 恢复时把
is_delete改回 0。 - 清理时批量调用 HDFS
fs.delete(new Path(hdfsPath), true),然后删除数据库记录。
注意一个坑:如果多个文件共享同一个 HDFS 路径(比如不同用户上传了同名文件),你硬删一个会把另一个也删了。所以建表时就要保证hdfs_path对每条记录唯一,或者在路径里加上用户 ID 和随机后缀。
5. 避坑指南:Hadoop 云盘踩坑最多的五个地方
5.1 上传文件后页面报 500,日志里是FileAlreadyExistsException
现象:同一个文件重复上传,第二次直接报“文件已存在”。
原因:HDFS 的fs.create默认不允许覆盖已有文件。项目里写死了fs.create(path, false)。
解决:改成fs.create(path, true),或者在创建前先fs.delete(path, true)。但要注意覆盖前确认是不是同一个用户、同一个目录,避免误删。
5.2 启动后jps没有 DataNode,只有 NameNode
现象:格式化能过,但 DataNode 进程就是起不来,日志提示Initialization failed for Block pool.
原因:伪分布式模式下,多次格式化 NameNode 导致clusterID不匹配。格式化会生成新的 clusterID,DataNode 本地存的 clusterID 还是旧的。
解决:先删掉dfs.datanode.data.dir下的所有数据和dfs.namenode.name.dir下的数据,再重新hdfs namenode -format。血泪经验:格式化之前先备份数据。
5.3 上传大文件时 Tomcat 直接报MaxUploadSizeExceededException
现象:文件超过几百 MB,前端显示上传失败,后端日志是 Tomcat 的 maxPostSize 异常。
原因:Tomcat 默认限制单个 POST 请求 2MB(maxPostSize),分片没做或者配置文件没改。
解决:如果你做完了分片上传,每片控制在 8MB 以内,但 Tomcat 默认 2MB 依然不够。需要修改 Tomcat 的server.xml里<Connector>加maxPostSize="0"或maxSwallowSize="104857600"。注意0表示不限制,但也意味着可能被恶意请求拖垮,建议设一个合理值如 100MB。
5.4 下载的文件 MD5 和原文件不一致
现象:上传下载都能完成,但下载文件大小小了或者文件损坏。
原因:多半是上传时没有用二进制流,或者读取 HDFSFSDataInputStream时用了字符流导致数据被转码。
解决:上传下载全部用IOUtils.copyBytes,不要自己写readLine。另外检查代码里有没有对OutputStream做flush。我习惯在写完最后一块后调用out.hflush(),然后out.close(),确保客户端缓冲全部落盘。
5.5 用户删除文件后,HDFS 空间没有释放
现象:删除了大量文件,hdfs dfs -du看空间还是没变化。
原因:只做了数据库软删除,没调 HDFS 删除接口。
解决:确认你的删除逻辑里有fs.delete(path, true)执行,并且 DataNode 的垃圾回收(dfs.trash.interval)默认 0,表示不回收。如果你开了 HDFS Trash,删除后文件会进.Trash占空间,需要手动清空或设置周期。
6. 进阶技巧:用 distcp 做集群间备份和迁移
当你把这个云盘项目真正用起来,用户上传的文件会越来越多。伪分布式只有一台机器,数据安全性很差。我一般会给项目加一个自动备份机制:把线上集群的 HDFS 数据定期同步到另一台备用机器或对象存储。这里要用到 Hadoop 自带的distcp工具。
先看最常用的命令:
hadoop distcp -update -delete hdfs://192.168.1.10:9000/user hdfs://192.168.1.11:9000/user参数说明:-update表示只同步源端比目标端新的文件;-delete表示删除目标端多出来的文件——这个参数适合做镜像同步,但要注意,如果你之前误删了源端数据,-delete会把目标端也删掉。所以我一般把它拆成两步:
# 第一步:增量同步 hadoop distcp -update hdfs://192.168.1.10:9000/user hdfs://192.168.1.11:9000/user # 第二步:验证数据量 hadoop distcp -numListstatusThreads 10 -stats /tmp/distcp_stats第二步不是真正验证数据完整性,但可以通过-stats输出同步的文件数、字节数。你要是想验证每个文件的 MD5,得自己写脚本:
hdfs dfs -ls -R /user > /tmp/source_list while read line; do src_path=$(echo $line | awk '{print $NF}') src_md5=$(hdfs dfs -cat $src_path | md5sum) tgt_md5=$(hdfs dfs -cat ${src_path/192.168.1.10/192.168.1.11} | md5sum) if [ "$src_md5" != "$tgt_md5" ]; then echo "Mismatch: $src_path" fi done < /tmp/source_list注意这种方式只适合小数据量,大数据量还是靠hadoop distcp自带的校验机制:它在拷贝时会对每个文件做 CRC32 校验,-update只复制大小或时间戳不同的文件。所以日常同步,我推荐直接跑-update,每个月做一次全量校验,防止源端数据静默损坏。
另外还有一个参数值得记住:-m控制并发 map 数量。默认每个文件一个 map,如果你的文件数量特别多(比如几十万个小文件),可以加-m 20提高并发;但伪分布式只有 2 个核心的话,并发太高反而会把节点压垮。我一般取 5~10。
最后说说我的习惯:每次修改了元数据表结构或者调整了 HDFS 目录策略,我都要先跑一遍hdfs dfsadmin -report看数据节点状态,再对网盘里最有代表性的几个文件做上传下载验证。从那以后,这个项目再也没出现过“页面正常但文件丢了”的翻车。希望这份笔记能帮你在复现或者改造时少走几步弯路。
本文还有配套的精品资源,点击获取