Hadoop+SSH框架实现HDFS网盘:从环境搭建到Java API实战 简介这份资源面向大数据与分布式系统方向的开发者及高校学生提供一套基于Hadoop平台、借助SSH框架实现HDFS网盘的完整项目源码帮助读者理解分布式存储与远程安全访问的落地方式。压缩包共629个文件约37.4MB涵盖60个java源文件、61个class编译文件、51个jsp页面、97个jar依赖包以及png、gif等界面素材和xml、js、css等前端与配置资源结构上包含文件服务、用户管理、HDFS操作与监控等模块便于对照学习SSH与HDFS的整合思路。目前已有132人学习下载。读者可从中获取Hadoop集群配置、SSH免密通信、HDFS客户端工具开发及网盘功能实现的完整参考适合作为课程设计、毕业设计或分布式存储实践的对照案例也可用于排查节点通信与文件读写中的常见问题。1. 从一份课程设计说起Hadoop SSH 框架做 HDFS 网盘到底在做什么很多人第一次看到「基于 Hadoop利用 SSH 框架实现 HDFS 网盘」这个题目第一反应是懵的SSH 不是远程登录工具吗怎么就成了「框架」其实这里的 SSH 指的是 Struts2 Spring Hibernate 这套经典的 Java Web 三层组合和 Linux 里那个ssh命令完全是两码事这也是这个标题最容易让人翻车的地方。它要解决的核心问题是把 Hadoop 分布式文件系统 HDFS 当成后端存储用一套 Java Web 应用把它包装成普通用户能用的网盘——上传、下载、浏览目录、删除、重命名前端点一下后端通过 HDFS Java API 落到集群上。这套东西适合谁适合正在做大数据课程设计的学生、想理解 HDFS 读写流程的初学者以及需要给内部团队搭一个轻量文件共享入口的工程师。它不追求高并发和商业级体验但能把「HDFS 编程实践」这条链路完整走通从伪分布式搭建、SSH 免密、HDFS 常用命令到用 Java 代码调FileSystem对象做增删改查。下面我按自己踩过的顺序把这条路拆开讲清楚。2. 环境先立住Hadoop 伪分布式搭建与 SSH 免密登录2.1 为什么先搭伪分布式而不是直接上集群做课程设计或者本地验证没必要一上来就搞多节点。伪分布式Pseudo-Distributed在单台机器上把 NameNode、DataNode、ResourceManager、NodeManager 全部跑起来用的是真实 HDFS 的读写流程只是副本数默认 1。它的价值在于你能用hdfs dfs命令真实操作文件也能用 Java API 连上去代码和真集群几乎不用改。常见做法是 Ubuntu 或 CentOS 8 上装 JDK8 Hadoop 3.x配好core-site.xml、hdfs-site.xml、mapred-site.xml、yarn-site.xml四个文件。这里有个血泪经验Hadoop 对 JDK 版本敏感JDK8 最稳JDK11 以上容易在启动时抛IllegalAccessError。另外主机名不要用下划线hostname里带_会导致 DataNode 注册失败这是很多人排查半天的玄学问题。2.2 SSH 免密登录Hadoop 启动脚本的硬依赖Hadoop 的start-dfs.sh、stop-dfs.sh这些脚本内部会通过 SSH 去连本机或其他节点所以必须配免密。注意这里的 SSH 是 Linux 的远程登录工具和后面 Web 层的 SSH 框架同名但无关别搞混。# 生成密钥对一路回车即可 ssh-keygen -t rsa -P -f ~/.ssh/id_rsa # 把公钥追加到授权文件实现本机免密 cat ~/.ssh/id_rsa.pub ~/.ssh/authorized_keys chmod 600 ~/.ssh/authorized_keys # 验证第一次会问 yes/no之后应直接登录不输密码 ssh localhost逻辑说明ssh-keygen生成私钥id_rsa和公钥id_rsa.pub-P 表示空密码短语方便脚本自动调用。authorized_keys权限必须是 600否则 SSH 会以「权限过于开放」为由拒绝报Authentication refused: bad ownership or modes。验证时如果还要输密码检查~/.ssh目录权限是否为 700。参数上-t rsa指定密钥类型现在也可以换ed25519但 Hadoop 生态里 rsa 兼容性最好。如果你在 CentOS 8 上遇到ssh localhost连不上先确认sshd服务在跑sudo systemctl restart sshd再看防火墙有没有拦 22 端口。2.3 四个配置文件的最小改动伪分布式不需要改太多关键是core-site.xml里指定fs.defaultFS为hdfs://localhost:9000hdfs-site.xml里把dfs.replication设为 1。改完执行hdfs namenode -format格式化再start-dfs.sh。用jps应该能看到 NameNode、DataNode、SecondaryNameNode 三个进程。少一个就去看logs目录下对应的.log八成是端口占用或者主机名解析问题。提示格式化只能做一次重复格式化会导致 NameNode 和 DataNode 的 clusterID 不一致DataNode 起不来。真格式化了就删掉data和name目录重来。3. 后端打通用 HDFS Java API 封装网盘的文件操作3.1 选型理由为什么用 FileSystem 而不是 WebHDFSHDFS 对外提供两种编程入口一是 REST 风格的 WebHDFS二是原生 Java API。网盘这种场景后端本来就是 Java Web直接引hadoop-client依赖用FileSystem对象最顺手不用额外处理 HTTP 认证和 JSON 解析。WebHDFS 更适合非 Java 语言或者跨网络调用。所以这里选 Java API配合 Spring 管理FileSystem这个重量级对象的生命周期。核心依赖就一个版本和你集群保持一致dependency groupIdorg.apache.hadoop/groupId artifactIdhadoop-client/artifactId version3.3.4/version /dependency版本号必须和集群里hadoop version输出一致否则会出现Protocol mismatch或者序列化异常。这是新手最容易忽略的点本地能跑、一上服务器就崩多半是版本对不上。3.2 上传与下载把 InputStream 交给 HDFS网盘最核心的两个动作就是上传和下载。上传是把本地文件流写进 HDFS下载是反过来。下面这段代码是 Service 层的骨架用 Spring 注入FileSystem。// 上传localPath 本地文件hdfsPath HDFS 目标路径 public void upload(String localPath, String hdfsPath) throws IOException { // 目标已存在则覆盖 Path dst new Path(hdfsPath); try (InputStream in new FileInputStream(localPath)) { // 第三个参数 true 表示覆盖已存在文件 fs.copyFromLocalFile(false, true, new Path(localPath), dst); } } // 下载把 HDFS 文件写到本地输出流 public void download(String hdfsPath, OutputStream out) throws IOException { Path src new Path(hdfsPath); try (FSDataInputStream in fs.open(src)) { IOUtils.copyBytes(in, out, 4096, false); } }逻辑说明copyFromLocalFile的第一个布尔参数是「是否删除本地源文件」网盘场景必须传false否则用户上传完本地文件就没了这是灾难级 bug。第二个参数是「是否覆盖」传true。IOUtils.copyBytes的第三个参数是缓冲区大小4096 是默认值大文件可以调到 8192 或 16384 提升吞吐但别太大否则内存吃紧。第四个参数false表示不自动关闭流交给 try-with-resources 处理。参数上fs.open返回的FSDataInputStream支持seek做断点续传时用得上。如果下载大文件出现Premature EOF通常是 DataNode 磁盘满或者网络抖动先查hdfs dfsadmin -report看剩余空间。3.3 目录浏览与删除listStatus 和 delete 的边界网盘要展示文件列表用listStatus拿到FileStatus数组里面包含文件名、大小、修改时间、副本数等信息。// 列出目录下所有文件过滤掉目录本身 public ListMapString, Object list(String hdfsDir) throws IOException { ListMapString, Object result new ArrayList(); for (FileStatus status : fs.listStatus(new Path(hdfsDir))) { MapString, Object item new HashMap(); item.put(name, status.getPath().getName()); item.put(size, status.getLen()); item.put(isDir, status.isDirectory()); item.put(mtime, status.getModificationTime()); result.add(item); } return result; } // 删除recursive 决定是否递归删目录 public boolean delete(String hdfsPath, boolean recursive) throws IOException { return fs.delete(new Path(hdfsPath), recursive); }逻辑说明listStatus返回的是该目录的直接子项不递归。如果目录不存在会抛FileNotFoundException前端调用前最好先fs.exists判断一下。delete的第二个参数很关键删空目录传false可以删非空目录必须传true否则返回false且不报错用户以为删了其实没删这是典型的静默失败。删除操作不可逆HDFS 没有回收站除非开了 Trash 机制所以生产环境建议先做二次确认。注意FileSystem对象是线程安全的但不要每次请求都FileSystem.get()新建开销很大。用 Spring 单例管理配置里指定fs.defaultFS让它连到你的 NameNode。4. Web 层整合Struts2 Spring 把 HDFS 操作暴露成网盘接口4.1 SSH 框架里各层怎么分工这套「SSH 框架」里Struts2 负责接收前端请求和结果跳转Spring 负责依赖注入和事务Hibernate 理论上管关系型数据库。但网盘的文件元数据其实都在 HDFS 里Hibernate 在这里更多是存用户表、权限表、操作日志。很多人纠结 Hibernate 要不要用我的建议是如果只是课程设计用户登录和文件记录用 Hibernate 存 MySQL 就够了别硬套复杂映射。分层上Action 层只做参数校验和调用 ServiceService 层封装 HDFS 操作DAO 层走 Hibernate。这样职责清晰也符合课程设计对「框架整合」的考察点。4.2 Struts2 Action 接收上传文件Struts2 的文件上传靠拦截器Action 里定义三个属性File类型的上传文件、文件名、内容类型。public class FileAction extends ActionSupport { private File upload; // 临时文件 private String uploadFileName; // 原始文件名 private String uploadContentType; // MIME 类型 private String currentDir /; // 当前目录 // setter 省略Struts2 靠 setter 注入 public String doUpload() { // 目标路径拼上原始文件名 String dst currentDir uploadFileName; try { hdfsService.upload(upload.getAbsolutePath(), dst); return SUCCESS; } catch (IOException e) { addActionError(上传失败 e.getMessage()); return ERROR; } } }逻辑说明Struts2 会把上传的文件先落到临时目录upload指向这个临时文件uploadFileName才是用户看到的原始名。所以上传时要用upload.getAbsolutePath()作为本地源路径目标路径用currentDir uploadFileName拼接。注意文件名里如果有中文或特殊字符HDFS 路径可能出问题建议做一次 URL 编码或者用 UUID 重命名。参数上struts.xml里要配fileUpload拦截器并设置maximumSize限制单文件大小默认是 2MB网盘场景肯定不够调到 100MB 以上。同时struts.multipart.maxSize也要改两个地方都改才生效只改一个会报File too large。4.3 Spring 管理 FileSystem 单例FileSystem的创建成本高必须交给 Spring 做单例。配置类里这样写Configuration public class HadoopConfig { Value(${hdfs.defaultFS}) private String defaultFS; Bean public FileSystem fileSystem() throws IOException { Configuration conf new Configuration(); conf.set(fs.defaultFS, defaultFS); // 避免本地库缺失警告 conf.set(fs.hdfs.impl, org.apache.hadoop.hdfs.DistributedFileSystem); return FileSystem.get(conf); } }逻辑说明FileSystem.get(conf)会根据fs.defaultFS的 scheme 返回对应实现hdfs://开头就返回DistributedFileSystem。显式设置fs.hdfs.impl是为了避免某些环境下加载到错误的实现类。这个 Bean 是单例整个应用共用一个连接性能最好。参数上defaultFS建议写在application.properties里格式hdfs://你的主机名:9000。如果连不上先确认 NameNode 的 9000 端口通不通telnet一下。另外客户端机器要能解析 NameNode 的主机名/etc/hosts里加一条映射否则会报UnknownHostException。5. 避坑排查HDFS 网盘落地时最容易翻车的 5 个点5.1 上传成功但文件大小是 0现象前端提示上传成功去 HDFS 一看文件存在但getLen()返回 0。原因通常是 Struts2 的临时文件在 Action 执行完就被删了而你的上传逻辑是异步的或者延迟执行的流已经关了。解决在 Action 方法内同步完成上传不要丢给线程池异步处理或者先把临时文件复制到自己的目录再操作。5.2 下载中文文件名乱码现象下载下来的文件名变成%E6%96%87%E4%BB%B6这种。原因是 HTTP 响应头里的Content-Disposition没有正确编码。解决用URLEncoder.encode(fileName, UTF-8)编码后再塞进响应头同时把filename*用 RFC 5987 格式写浏览器才认。5.3 DataNode 启动后立刻挂掉现象jps里 DataNode 一闪而过日志报Incompatible clusterID。原因是重复执行了hdfs namenode -formatNameNode 的 clusterID 变了DataNode 还是旧的。解决删掉dfs.datanode.data.dir指向的目录重新启动 DataNode它会重新注册。记住格式化只做一次。5.4 SSH 免密配了但 Hadoop 启动还要密码现象ssh localhost能免密但start-dfs.sh还是提示输密码。原因是脚本用的是ssh到当前主机名而不是localhost而你的公钥只对localhost生效。解决把公钥也追加到authorized_keys对应的主机名解析上或者直接ssh-copy-id 你的主机名。检查hostname和/etc/hosts是否一致。5.5 大文件上传内存溢出现象上传几百 MB 文件时抛OutOfMemoryError。原因是代码里用了FileUtils.readFileToByteArray把整个文件读进内存。解决全程用流式处理copyFromLocalFile本身就是流式的别自己再包一层字节数组。Struts2 的maximumSize也要同步调大否则拦截器层就拦掉了。6. 进阶技巧用副本数和块大小调优你的网盘体验把基本功能跑通之后真正拉开差距的是对 HDFS 参数的把控。网盘场景下用户上传的文件大小差异极大从几 KB 的文档到几个 GB 的视频都有统一用默认的 128MB 块大小和 3 副本并不划算。我的习惯是按目录做差异化配置普通文档目录副本数设 2 省空间重要资料目录设 3 保可靠临时目录设 1。设置副本数用fs.setReplication(path, (short) 2)注意这个操作是异步的NameNode 会调度 DataNode 去复制不会立刻生效。块大小在写入时通过Configuration指定但已经写入的文件改不了块大小只能重写。所以上传前就要想清楚别等文件堆了几百 GB 再后悔。验证副本是否生效用hdfs fsck /path -files -blocks看每个块的副本分布输出里Live_repl就是当前存活副本数。如果小于你设的值说明有 DataNode 掉线或者磁盘满了去hdfs dfsadmin -report里看Under replicated blocks的数量。还有一个容易被忽略的点小文件问题。网盘里如果全是几 KB 的文件每个文件都会在 NameNode 里占一条元数据几十万个小文件会把 NameNode 内存吃光。常见做法是定期用 MapReduce 或 Spark 做小文件合并把同目录下的小文件打包成一个大文件比如 SequenceFile 或 HAR 归档。课程设计里不一定要做但面试被问到「HDFS 为什么不适合存大量小文件」时这就是标准答案。最后说个我自己的习惯每次改完 HDFS 配置先在一份测试目录上验证读写确认没问题再动生产目录。HDFS 的删除没有后悔药skipTrash一旦生效就是真删。我一般会在 Service 层加一层软删除逻辑删除时先移到/trash目录定时任务再清理给自己留条后路。这套方案值不值得做如果你要吃透 HDFS 读写流程和 Java API它是最短的路径如果只是想要个网盘现成方案更省事。希望帮到你。本文还有配套的精品资源点击获取