
简介基于Hadoop实现的百度云盘毕设项目包含完整源代码与配套文档适合计算机相关专业在校学生、毕业设计者及大数据初学者学习参考。资源包共2000个文件压缩后77.11MB涵盖Java源码、JSP动态页面、JS/CSS前端交互样式、HTML页面及JAR依赖库等源码经过测试运行成功可直接用于课程设计、项目演示或二次开发。已有535人学习下载资源按Eclipse工程结构组织内置README说明文档并配有部署说明方便快速理解项目模块与运行流程。借助该资源读者既能掌握Hadoop分布式存储与大文件分块上传下载的实现思路也能借鉴一个完整Web系统从后端调度到前端展示的交互与权限管理设计适合作为毕设修改和大数据实战入门的参考资料。1. 基于Hadoop的百度云盘拿HDFS当网盘底座到底行不行做个仿百度云盘的课程设计或实训项目底层存储不选MySQL也不选MinIO而是直接压在Hadoop的HDFS上——这个搭配乍一听有点怪因为HDFS的吞吐模型是为大文件顺序读写设计的元数据操作能力很弱恰好踩中网盘系统最敏感的两块海量小文件和秒级列目录。但换个角度想网盘真正的刚需是“文件放得下、不丢、能按需取”这一点HDFS的分布式冗余和流式读写恰好是强项。把文件分块、把元数据抽到独立表、把HDFS目录结构设计得足够扁平这套方案完全能支撑起一个课程设计级别的完整网盘系统而且面试时还能顺手讲清楚HDFS的Block、副本策略和NameNode内存模型。这篇文章就把这套方案从架构选型、环境搭建到上传下载代码和踩坑记录完整过一遍适合正在做Hadoop课程设计、毕业设计或者想快速搭一个私有网盘练手的人。2. 架构与选型把Hadoop塞进网盘系统的四个关键决定2.1 客户端形态Web端配HDFS后端统一封装存储操作常见做法是Spring Boot做后端、Vue做前端Hadoop通过HDFS Java API接入。有人会问要不要走WebHDFS的REST接口我的建议是课程设计和中小型项目直接上Java API因为WebHDFS多一层HTTP转发定位问题时分不清是网络问题还是HDFS问题而Java API的报错信息直接给到DataNode级别排查起来清楚得多。桌面端不是不能做但要注意客户端机器的Java版本、Hadoop native库和服务器端必须对齐否则跑起来各种Unsupported class file major version纯属给自己添堵。后端封装一个HdfsStorageService所有Controller只跟这个Service打交道上层不感知Hadoop的存在。这样后期想把存储层换成MinIO或FastDFS改动范围控制在一个类里。伪分布式开发阶段后端服务和NameNode跑在同一台机器数据本地读写性能上不会有明显瓶颈。2.2 元数据设计文件名直接进HDFS路径还是单独建元数据表这是整个项目最核心的选型直接决定系统能不能撑住网盘的基本操作。网盘要求秒级列目录、改名、移动、搜索而HDFS的listStatus在目录层级深、文件数量大时延迟明显NameNode要遍历内存中的文件树目录一深就慢。所以我的做法是MySQL建一张file_meta表存文件ID、用户ID、逻辑路径、文件名、扩展名、大小、上传时间、父目录IDHDFS上只存物理文件物理文件名用UUID重命名落盘路径和用户逻辑目录完全脱钩。CREATE TABLE file_meta ( id BIGINT PRIMARY KEY AUTO_INCREMENT, user_id BIGINT NOT NULL, parent_id BIGINT DEFAULT 0 COMMENT 父目录ID0表示根目录, file_name VARCHAR(255) NOT NULL COMMENT 用户看到的文件名, file_ext VARCHAR(20) DEFAULT COMMENT 扩展名目录则为空, hdfs_path VARCHAR(500) NOT NULL COMMENT HDFS物理路径, file_size BIGINT DEFAULT 0, is_dir TINYINT DEFAULT 0 COMMENT 0文件 1目录, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, KEY idx_user_parent (user_id, parent_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;这个表设计里有几个点要注意。parent_id存的是本表自增ID不是HDFS路径这样目录改名、移动时只需要更新一行记录不用动HDFS。hdfs_path存物理路径用户无论怎么改逻辑目录物理路径都不变。所有涉及“查目录”“搜文件”的请求全部打在MySQL上HDFS只承担真正的文件内容读写两边各干各擅长的事。2.3 大文件切分HDFS的Block和网盘分块上传怎么配合很多人把这两个“块”搞混。HDFS的Block默认128MB是存储层的数据分布单元一个文件被切成若干个Block分散到不同DataNode上这个切分对上层完全透明。而网盘的分块上传是应用层行为前端把大文件切成几MB一块逐块传到后端目的是断点续传和失败重传。两者不是一回事但可以配合前端分块上传到后端后端先落本地临时目录所有分块到齐后按顺序流式写入HDFS让HDFS再去切它的Block。// 分块合并写入HDFS的示意代码 Path target new Path(hdfsPath); try (FSDataOutputStream out fs.create(target, true)) { for (File part : sortedParts) { try (FileInputStream in new FileInputStream(part)) { IOUtils.copyBytes(in, out, 4 * 1024 * 1024, false); } } }这里有一个参数值得留意fs.create的第二个参数是overwrite分块合并场景必须传true因为如果同一任务重试HDFS上可能已经存在一个半截文件。IOUtils.copyBytes的buffer设成4MB和前端分块大小对齐减少磁盘到网络的拷贝次数。所有分块合并完毕后再删除本地临时文件避免中途失败连底稿都没了。2.4 命名策略用UUID拼用户ID从源头杜绝路径冲突HDFS物理路径固定为/user/{userId}/files/{uuid}.bin三段式结构。用户ID做顶层隔离UUID保证文件名全局唯一.bin后缀只是占位真正的文件名、扩展名全在元数据表里。这样设计后用户随意建同名目录、传同名文件、多层嵌套深目录都不会产生HDFS路径冲突。这套策略还有个额外好处分享功能好做。分享时只需生成一个短链接把file_meta.id带进去别人点开链接时后端拿ID查表、拼物理路径、流式输出HDFS物理路径永远不会暴露给前端。如果直接拿用户原始文件名拼HDFS路径一旦文件名带特殊字符或路径穿越写法轻则目录混乱重则被利用覆盖别人的文件。3. 环境搭建与目录初始化先跑通伪分布式再决定要不要上集群3.1 基于Ubuntu的Hadoop伪分布式环境配置课程设计阶段最常见的是Hadoop伪分布式搭建也就是在一台机器上同时跑NameNode、DataNode和SecondaryNameNode三个进程。网上教程很多但配置项版本差异极大我一般按最小必要集来配核心就三个文件。# core-site.xml configuration property namefs.defaultFS/name valuehdfs://localhost:9000/value /property /configuration # hdfs-site.xml configuration property namedfs.replication/name value1/value /property property namedfs.namenode.name.dir/name value/home/hadoop/data/name/value /property property namedfs.datanode.data.dir/name value/home/hadoop/data/data/value /property /configuration # 格式化并启动 hdfs namenode -format start-dfs.sh jpsdfs.replication在伪分布式下必须设成1因为只有一台DataNode设成默认3的话副本永远凑不齐会一直报Not replicated警告。dfs.namenode.name.dir和dfs.datanode.data.dir建议放到Hadoop安装目录之外因为format操作会清空name目录放在安装目录里容易误删整个集群数据。jps是验证进程是否启动的最快手段能看到NameNode、DataNode和SecondaryNameNode三个进程就说明伪分布式起来了。3.2 HDFS目录结构设计与初始化代码伪分布式跑通后先别急着写业务代码把HDFS的目录骨架建好后面所有上传下载都往这个结构里填。开发阶段可以用hdfs dfs -mkdir -p手动建但更推荐在项目启动时用Java代码自动初始化这样换一台机器部署不用再敲一遍命令。Component public class HdfsInitializer implements ApplicationRunner { Autowired private FileSystem fs; Override public void run(ApplicationArguments args) throws IOException { Path root new Path(/user); if (!fs.exists(root)) { fs.mkdirs(root); fs.setPermission(root, new FsPermission((short) 0777)); } // 预留一个回收站目录用于软删除 Path trash new Path(/trash); if (!fs.exists(trash)) { fs.mkdirs(trash); } log.info(HDFS目录初始化完成); } }目录骨架就两个/user放所有用户文件/trash做软删除中转站。这里有个权限细节/user给到0777因为用户ID是动态创建的注册新用户时再单独mkdirs出来统一给一次根目录权限能省去每次授权的麻烦。生产环境这么干肯定不行但课程设计场景下完全够用。3.3 Hadoop和Zookeeper整合从伪分布式走向高可用如果你的项目是毕业设计或者想写进简历的实训作品建议在伪分布式基础上再叠加Hadoop和Zookeeper整合实战也就是HA高可用模式。原理是让两台NameNode一主一备Zookeeper负责监控状态主节点挂掉后自动切换。这个点面试时极加分因为很多人只会搭伪分布式能讲清楚HA的人明显少一个身位。HA搭建的常见做法是两台物理机或虚拟机各跑一个NameNode和JournalNode再单独起一台装Zookeeper集群。配置上比伪分布式多出dfs.ha.namenodes、dfs.namenode.shared.edits.dirJournalNode地址、dfs.client.failover.proxy.provider这几组参数。需要注意HA模式下不能执行hdfs namenode -format要先在备节点执行hdfs namenode -bootstrapStandby同步元数据这一步容易翻车多留十分钟排错时间。4. 网盘核心接口实现上传、下载、列表三段代码打通全流程4.1 上传接口本地临时分块顺序合并写入HDFS文件上传是整个网盘最重的链路我走的路径是前端切块 → 后端收块落临时目录 → 全部到齐后合并写HDFS → 写元数据表 → 清理临时文件。前端负责把文件切成5MB一块后端接口接收分块序号和分块数据存到/tmp/upload/{taskId}/{partIndex}等所有分块到齐后触发合并。PostMapping(/upload/merge) public ResultVoid merge(RequestBody MergeRequest req) throws IOException { // 1. 生成HDFS物理路径UUID避免重名 String hdfsPath String.format(/user/%d/files/%s.bin, req.getUserId(), UUID.randomUUID()); Path target new Path(hdfsPath); // 2. 遍历分块文件按序号排序后顺序写入 File tempDir new File(/tmp/upload/ req.getTaskId()); File[] parts tempDir.listFiles((dir, name) - name.endsWith(.part)); if (parts null || parts.length 0) { throw new BusinessException(分块文件不存在请重新上传); } Arrays.sort(parts, Comparator.comparingInt(f - Integer.parseInt(f.getName().split(\\.)[0]))); try (FSDataOutputStream out fs.create(target, true)) { for (File part : parts) { try (FileInputStream in new FileInputStream(part)) { IOUtils.copyBytes(in, out, 4 * 1024 * 1024, false); } } } // 3. 合并成功后元数据入库 FileMeta meta new FileMeta(); meta.setUserId(req.getUserId()); meta.setFileName(req.getFileName()); meta.setHdfsPath(hdfsPath); meta.setFileSize(req.getFileSize()); fileMetaMapper.insert(meta); // 4. 清理本地临时分块 FileUtils.deleteDirectory(tempDir); return Result.ok(); }逻辑说明合并操作最关键的是分块排序文件名格式是0.part、1.part这种纯数字前缀直接按字符串排序也行但位数不同时10.part会排在2.part前面所以要用Comparator解析成整数再排。排序错了文件内容就错位而且这种错位在写入时不会报错只有下载打开文件才发现是坏的。FileUtils.deleteDirectory是最后一步本地临时目录没清干净的话跑几天磁盘就满了。参数说明IOUtils.copyBytes的第四个参数是true表示拷贝结束后关闭流这里传false是因为out流在循环外统一关提前关掉后面分块就没法写了。分块大小和buffer对齐到5MB磁盘读写的缓冲区过大反而导致GC压力这个体量够用。4.2 下载接口流式输出文件名从元数据表还原下载接口的核心是“HDFS拿流HTTP回写”不能在内存里把整个文件读出来再返回几十MB的文件就把JVM堆打爆。正确做法是把FSDataInputStream直接接入HttpServletResponse的输出流边读边写。GetMapping(/download) public void download(RequestParam Long fileId, HttpServletResponse response) throws IOException { FileMeta meta fileMetaMapper.selectById(fileId); if (meta null) { throw new BusinessException(文件不存在); } // 还原用户看到的文件名处理中文和空格 String fileName URLEncoder.encode(meta.getFileName(), UTF-8) .replace(, %20); response.setHeader(Content-Disposition, attachment; filename*UTF-8 fileName); response.setContentType(application/octet-stream); response.setContentLengthLong(meta.getFileSize()); Path path new Path(meta.getHdfsPath()); try (FSDataInputStream in fs.open(path); ServletOutputStream out response.getOutputStream()) { IOUtils.copyBytes(in, out, 8 * 1024 * 1024, true); } catch (FileNotFoundException e) { log.error(HDFS文件不存在: {}, meta.getHdfsPath()); throw new BusinessException(文件已丢失请联系管理员); } }这个接口有两个容易踩的细节。第一个是中文文件名必须URLEncoder编码否则浏览器下载时中文名直接乱码第二个是setContentLengthLong要写对不写的话浏览器下载时没有进度条看起来像卡住了。IOUtils.copyBytes这里第三个参数传true让方法内部把两个流都关掉HDFS的连接用完即释放避免连接数被占满。4.3 列表接口查元数据表而不是list HDFS网盘首页打开瞬间要展示目录结构这个接口如果去调fs.listStatus每次都要把HDFS的目录树扫一遍文件多了之后延迟直线上升。正确姿势是查MySQL一条SQL搞定。SELECT * FROM file_meta WHERE user_id #{userId} AND parent_id #{parentId} ORDER BY is_dir DESC, create_time DESC;is_dir DESC让目录排前面这是网盘产品的通识文件按时间倒序排。目录和文件混在同一张表目录的file_size为0hdfs_path为空字符串。做重命名和移动时只需要更新这一行记录的file_name和parent_idHDFS完全不用动。列表接口响应时间从几百毫秒降到个位数毫秒这就是元数据分离的价值。5. 避坑指南Hadoop做网盘常见的五个坑5.1 小文件把NameNode内存吃光现象上传几百个小文件后NameNode日志频繁告警Web UI上的内存曲线飙升最终整个集群卡死。原因NameNode把整个文件系统的元数据都放在内存里每个文件、每个Block大概占用150字节左右单个DataNode的Block数量超过上限后集群直接进入安全模式。网盘场景天然产生大量小文件如果不处理几百个1KB的文件就能消耗几十MB内存。解决方案分三层。第一层上传入口限制最小分块小于1MB的文件合并写入一个物理文件里可以拼多个逻辑文件第二层定期用hdfs fsck扫描小文件比例对历史存量跑一次合并任务第三层NameNode堆内存调大hadoop-env.sh里HADOOP_NAMENODE_OPTS设置-Xmx4g起步但这是治标合并才是治本。5.2 删除文件后空间不释放回收站里堆满垃圾现象用户删了一个1GB的大文件HDFS的可用空间没有变化Web UI一看回收站目录越来越大。原因HDFS默认开启了回收站机制fs.trash.interval0表示不启用但如果配了非0值删除操作实际是把文件移到/user/{user}/.Trash目录不是真删。解决确认业务上是否真的需要回收站。网盘的“删除”功能本身有逻辑删除兜底元数据表里加个deleted字段就够HDFS层面直接物理删除更干净。在hdfs-site.xml里设置fs.trash.interval0禁用回收站或者定时任务执行hdfs dfs -expunge强制清空。5.3 伪分布式启动时DataNode进程起不来现象start-dfs.sh执行后jps只看到NameNode没有DataNode日志里报Incompatible clusterIDs。原因这是伪分布式搭建时最经典的翻车现场。第一次hdfs namenode -format生成了一个clusterID后来因为配置改动又format了一次NameNode换了新clusterID但DataNode的数据目录里还留着旧clusterID两边对不上就拒绝启动。解决把dfs.datanode.data.dir配置的目录整个删掉然后重新执行hdfs namenode -format。注意name目录和data目录都要删干净只删一个等于没删。这个坑几乎每个搭伪分布式的人都会踩一次踩过一次就记住了format之前先看清楚当前目录结构。5.4 Permission deniedHDFS权限报错排查半天现象后端代码调用fs.copyFromLocalFile上报错Permission denied: userroot, accessWRITE换成命令行hdfs dfs -put却正常。原因Java进程以root系统用户运行映射到HDFS的权限体系里就是root用户对/user/{userId}目录没有写权限。命令行hdfs dfs默认用当前系统用户如果之前用hdfs dfs -chmod 777授权过命令行的权限是够的但Java进程拿到的用户不同权限判断就过不去。解决两种思路。入门阶段直接在后端代码里设UserGroupInformation.createRemoteUser(hdfs)把所有HDFS操作统一以hdfs超级用户身份执行绕过权限判断进阶做法会给每个用户建独立的HDFS目录并设置owner走正规授权。课程设计推荐第一种三行代码解决问题把时间留给业务功能。5.5 上传大文件时客户端超时连接被NameNode掐断现象本地局域网传一个5GB的大文件传到一半抛SocketTimeoutException再看HDFS上只有个半截文件。原因HDFS客户端和DataNode之间的数据传输有超时限制默认的dfs.client.socket-timeout是60秒如果某段时间内网络抖动或者DataNode忙着写磁盘超过60秒没有心跳数据包连接就被判定超时断开。解决适当调大超时参数同时检查网络环境。property namedfs.client.socket-timeout/name value120000/value /property property namedfs.datanode.socket.write.timeout/name value180000/value /property顺带说一句很多人以为上传大文件超时是Hadoop配置问题调了半天参数发现原来是本地磁盘满了临时分块落不进去。遇到超时先看客户端所在机器的磁盘空间再怀疑Hadoop。6. 验证方案与后续扩展从“能跑”到“敢说”项目做完别急着交先按清单验一遍避免答辩现场翻车。基础功能清单单文件100MB上传确认HDFS上能看到对应Block数量和副本数并发场景用脚本模拟5个用户同时上传下载观察NameNode内存和DataNode IO是否平稳断点续传模拟传一半断网再恢复验证分块合并逻辑能正确识别缺失分块并重新拉起。进阶清单下载一个中文文件名文件确认浏览器拿到正常文件名重命名一个非空目录确认元数据表更新而HDFS物理路径不变停掉一个DataNode进程确认集群写入仍然正常。扩容方向有两个。小文件治理方面可以做一个后台定时任务扫描file_meta表里小于1MB的文件合并成SequenceFile存储元数据表加一个offset字段记录每个逻辑文件在物理文件里的偏移量。高可用方面如果用了Hadoop和Zookeeper整合可以写一个故障切换的验证脚本手动kill掉Active NameNode进程观察备节点能否在30秒内接管服务。最后说个做这个项目最值得养成的习惯每个向HDFS写入的接口都先想清楚“如果写了一半挂掉HDFS上会残留什么”。上传接口残留半截文件合并接口残留本地分块覆盖写残留垃圾Block。在代码里写清理逻辑比出了问题后再跑脚本扫目录省心得多。我早期做这个项目时偷懒没写清理一周后HDFS被临时文件塞爆只能一个个目录排查血泪教训。希望帮到你。本文还有配套的精品资源点击获取