ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

基于Hadoop的云盘系统开发实战:HDFS存储、元数据与避坑指南

基于Hadoop的云盘系统开发实战:HDFS存储、元数据与避坑指南 简介基于Hadoop的云盘系统是一套完整的大数据存储与管理项目源码面向正在学习Hadoop生态与分布式计算的开发者以及需要完成课程设计或毕业设计的计算机专业学生。资源以105个Java文件实现后端逻辑配合30个HTML、32个JS、13个CSS组成前端界面覆盖云盘服务接口、用户权限管理与文件分布式读写等核心功能另有75个GIF演示截图和少量JSON、properties、XML等配置文件便于理解运行效果与快速修改部署。压缩包共284个文件整体仅1.16MB结构紧凑适合导入IDE直接阅读调试。系统设计涉及NameNode/DataNode工作机制、YARN资源调度、数据分块存储与容错策略可在本地搭建伪分布式环境后验证完整流程。该资源已有100人学习作为轻量但完整的Hadoop云盘参考实现能帮助开发者快速掌握分布式存储落地思路。1. 基于Hadoop的云盘系统到底在做什么文件上传下载背后的分布式存储真相当你的课程设计题目是“基于Hadoop的云盘系统.zip”时第一反应可能是把文件用 Java API 写进 HDFS 就完事。但这个题目真正的重心不在“云盘”而在“基于 Hadoop”——你要面对的是 HDFS 的块存储、副本策略、NameNode 内存上限以及一个真实云盘该有的目录树、秒传、断点续传和并发控制。我见过太多人把项目做成“用 HDFS 当网盘的临时仓库”最后答辩时一句“如果用户上传大量小文件会怎样”就把人问住了。这个方向最适合正在做大数据课程设计、实训项目或准备 Hadoop 面试的人它能让你在一个真实业务里理解 HDFS 参数为什么会存在而不是只会执行 start-dfs.sh。2. 选型与整体架构为什么用 HDFS 做主存储元数据到底放哪2.1 三种元数据方案HBase、MySQL、Redis怎么选云盘系统本质上分两层文件二进制内容放 HDFS文件路径、大小、上传时间、所有者这些结构化信息放“元数据库”。选错元数据库项目完成度会差很多。最常见的三种方案各有使用场景。HBase 适合文件数超过亿级、你想把元数据也做成分布式的时候但代价是要额外维护一个 HBase 集群而且 HBase 的 rowkey 设计得不好就会出现热点典型问题是用文件 ID 做递增主键导致所有写入打在同一 Region。MySQL 是大多数人最稳妥的选择文件数在百万级内完全够用而且事务支持让你做目录重命名、删除文件时不用自己处理分布式一致性。Redis 适合做热数据缓存比如秒传校验、在线用户会话但只把 Redis 当唯一元数据库就会遇到 RDB 持久化丢数据的问题。我一般会这样拍板课程设计或中小规模实训项目元数据用 MySQLRedis 只做秒传和上传进度缓存如果题目明确要求“完整大数据生态”那就加 HBase并且把文件访问记录、回收站生命周期这类需要大扫描的数据放 HBase核心元数据仍然在 MySQL。这个取舍在答辩时反而加分因为你能说清楚为什么不是所有数据都扔进分布式组件。方案适合规模优点典型坑HBase亿级文件横向扩展、支持大扫描rowkey 热点、额外集群运维MySQL百万级事务、简单、生态成熟单表过大需要分表Redis万级热数据响应快持久化丢失、内存有限2.2 模块拆分客户端、API 层、存储层、任务调度一个能跑的基于 Hadoop 的云盘系统至少要有四个模块。客户端负责 Web 页面或桌面端的文件选择、分块、上传进度展示。API 层是 Spring Boot 的 Controller负责接收请求、校验权限、调用存储层。存储层封装 Hadoop 的 FileSystem API只向 API 层暴露 upload、download、list、mkdir 这几个方法。任务调度负责处理分块合并、垃圾回收、临时文件清理。这个拆分非常重要因为 HDFS 的写入是“一次性创建 追加”模式你不能像操作本地文件一样随时 seek 改写。我习惯在 API 层把上传请求拆成“初始化上传”和“上传分块”两个接口初始化时生成文件 ID 和分块列表上传分块时把数据先写到本地临时目录或 HDFS 的 /tmp 目录所有分块齐了以后再合并。这样做的好处是把 HDFS 的 append 操作限制在合并阶段避免频繁 append 导致的数据节点负载不均。下面是一个最小模块依赖示例如果你用 Spring Bootpom.xml 里需要引入的 Hadoop 客户端就长这样dependency groupIdorg.apache.hadoop/groupId artifactIdhadoop-client/artifactId version3.2.4/version /dependency dependency groupIdorg.apache.hadoop/groupId artifactIdhadoop-hdfs/artifactId version3.2.4/version /dependency注意这里不要用 hadoop-core 这个老坐标那是 Hadoop 1.x 时代的产物在 Hadoop 3.x 下引入会直接报 ClassNotFoundException。版本号也不是越大越好要和集群版本严格一致否则 RPC 协议握手会一直报 Failed to connect。我踩过用 3.3.6 客户端连 3.2.4 集群的坑后来统一成集群版本才解决。这就是为什么我建议先把集群版本定下来再去写 API 层代码。2.3 系统数据流一次分块上传经历了什么完整数据流是这样的用户选择文件客户端按配置好的分块大小默认 8MB切分同时计算每个分块的 MD5。API 层收到初始化请求后在 MySQL 元数据表插入一条记录状态是“上传中”并把文件 ID 返回给客户端。客户端逐块上传到 API 层API 层把数据临时写入 HDFS 的 /user/upload_tmp/{fileId}/block_x每写完一块就往 Redis 里记录进度。所有分块上传完成后API 层调用存储层的合并方法把这些 block_x 文件合并成目标文件写入最终目录 /user/cloud/fileId然后更新元数据表状态为“已上传”。为什么把临时目录设计成 fileId 下的 block_x 而不是直接分块写最终文件因为 HDFS 不支持随机写也不支持在文件中间插入数据。如果你直接把每块写成最终文件的分块文件那么合并时需要大量读取和重写而且一旦某个分块失败前序分块已经占用 NameNode 内存。用独立临时目录的好处是失败后只要在任务调度里定期扫描 age 超过 1 小时的临时目录并清理不会污染正式存储。这里还要提到一个和 ZooKeeper 的关系。如果你把系统扩展到 HA 模式NameNode 的自动故障转移依赖 ZooKeeper但云盘业务本身不强依赖 ZK。有些课程设计强行把系统拆成 NameNode、ResourceManager、ZooKeeper 一个个手动启动这是把简单问题复杂化了。Hadoop 伪分布式或完全分布式集群只需要 HDFSYARNZooKeeper 是在做 NameNode HA 时才需要。面试题里常问的“ZooKeeper 在 Hadoop 中有什么用”你答“用于 HA 状态协调、避免脑裂”就够用了。等你想做 Hadoop 和 ZooKeeper 整合实战时再引入 ZK 也不迟。2.4 接口定义与错误码设计云盘 API 层常见接口至少有初始化上传POST /api/file/init上传分块POST /api/file/upload合并POST /api/file/merge列出目录GET /api/file/list删除DELETE /api/file/{fileId}。接口设计时特别需要加上 merge 这个动作因为很多初学者只做 upload 和 download结果上传大文件时一次 HTTP 请求直接把 HDFS 写超时。我常用的 Controller 返回体是一个统一的 JSON 结构{code: 0, message: success, data: {...}}。其中错误码要细化至少区分文件已存在、上传中、分块缺失、HDFS 写失败、磁盘满。这些 code 在前端会决定是否弹出重试。比如分块缺失时前端只需要重新请求该分块而不需要重传整个文件。RestController RequestMapping(/api/file) public class FileController { PostMapping(/init) public ResultString init(RequestParam String fileName, RequestParam Long fileSize, RequestParam String md5) { String fileId fileService.initUpload(fileName, fileSize, md5); return Result.ok(fileId); } PostMapping(/upload) public ResultVoid upload(RequestParam String fileId, RequestParam Integer chunkIndex, RequestParam MultipartFile chunk) { fileService.saveChunk(fileId, chunkIndex, chunk); return Result.ok(); } }参数说明MultipartFile 在 Spring Boot 里会把文件块先写到临时目录再交给 service 层。这里有一个坑默认的spring.servlet.multipart.max-file-size是 1MB如果你使用 8MB 分块必须设置成大于分块大小否则文件块被 Spring 拒收报 FileSizeLimitExceededException。我会在 application.yml 里写spring: servlet: multipart: max-file-size: 64MB max-request-size: 256MB这里 64MB 允许前端一次性传 8 个分块256MB 是防止用户用超大请求把 Tomcat 连接池拖死。3. 环境准备与 Hadoop 集群搭建从伪分布式到三个必调参数3.1 伪分布式还是集群课程设计怎么选不翻车很多人在搭建环境时纠结用 docker 镜像、在线实训平台的安装课程还是自己手动搭。我的建议很直接如果你只有一台机器且内存小于 8GB就用伪分布式模式因为 Hadoop 生态组件对内存的占用远超你预期——光 NameNode 和 DataNode 默认各消耗 1GB 堆内存。如果你接触过“头歌 Hadoop 安装与配置”这类在线实训平台你会发现它的环境是预装好的而本地复现时最容易在 hostname、免密登录、PATH 配置上出错。伪分布式的本质是让每个角色都以独立 Java 进程运行在同一台机器上它们的配置和完全分布式完全一样区别只是 datanode 就是本机。我建议先跑通伪分布式再做一台 master 加一台 slave 的完全分布式这样你手里的系统在答辩时至少能说“支持水平扩展”。3.2 core-site.xml 和 hdfs-site.xml三个必调参数Hadoop 默认配置能启动但不适合云盘。文件上传下载是 IO 密集型业务所以以下三个参数我每次必调。第一个是 fs.defaultFS决定你的 FileSystem 根路径。在 core-site.xml 里configuration property namefs.defaultFS/name valuehdfs://hadoopmaster:9000/value /property property namehadoop.tmp.dir/name value/data/hadoop/tmp/value /property /configuration这里 hadoop.tmp.dir 非常重要。很多人用默认的 /tmp/hadoop-hadoop系统一重启或清理 /tmpNameNode 元数据就丢了表现为格式化后又能启动、但原来上传的文件全没了。你必须把它指向独立数据盘目录比如 /data/hadoop/tmp。这是第一个必调参数。第二个是副本数 dfs.replication。云盘场景下副本数不是越多越好默认 3 份在伪分布式单节点上会一直出现“块副本数不足”的告警。伪分布式或单 DataNode 时我把副本数调到 1完全分布式至少 2 个节点时调回 2 或 3。property namedfs.replication/name value1/value /property property namedfs.namenode.name.dir/name valuefile:///data/hadoop/namenode/value /property property namedfs.datanode.data.dir/name valuefile:///data/hadoop/datanode/value /property第三个是 dfs.blocksize云盘默认按 128MB 分块但大多数用户上传的是几 MB 的文档和图片。如果块太小NameNode 管理文件块数量就会飙升如果块太大大文件上传时临时合并的中间文件也会占用大量网络。我的经验是课程设计保持默认 128MB但如果你做的是照片分享类云盘可以调成 64MB。注意blocksize 只在创建文件时生效对已上传文件调整没有意义。3.3 格式化与启动顺序那些血泪细节每次修改 hdfs-site.xml 里的 name.dir 都要重新格式化 NameNode格式化前必须删除 data 目录里的历史数据否则会报 NameNode is not formatted 或元数据不匹配。格式化命令hdfs namenode -format -force-force 不是必须的但如果上次格式化残留了 current 目录不加会提示确认容易在脚本里卡住。启动顺序必须是先 NameNode 再 DataNode或者直接用 start-dfs.sh。如果你先启动了 DataNode它会注册到 NameNode没问题但如果你改了配置后只重启 DataNode 而没重启 NameNodeDataNode 就会一直处于 In Service but stuck in safe mode 的状态。验证集群是否就绪执行hdfs dfsadmin -report这个命令会打印每个 DataNode 的容量、剩余空间、上次心跳时间。如果出现 No datanodes to serve优先检查 NameNode 和 DataNode 的进程是否都在其次检查 hostname 是否绑定到 0.0.0.0。提示伪分布式最容易忽略的是 hostname 映射。如果你的机器 hostname 是 hadoopmaster那么 /etc/hosts 里必须有127.0.0.1 hadoopmaster否则 NameNode 会尝试连接一个无法解析的地址表现为 9000 端口一直在监听但客户端连不上。还有一点和 ZooKeeper 整合有关的经验如果你只是做云盘不要一上来就配置 HA因为 HA 需要至少三台机器部署 ZK而且 NameNode 的 JournalNode 同步一旦没配置好会导致两个 NameNode 都处于 standby 状态。先把单 NameNode 用熟面试题里问“NameNode HA 怎么实现”时你能说出 DFSZKFailoverController 和 journalnode 就够了这比强行搭一套不稳定 HA 更有说服力。3.4 在线实训平台与本机集群的差异如果你在头歌这类平台上做过 Hadoop 安装与配置你会发现平台已经配好 JAVA_HOME 和 HADOOP_HOME你只需要执行命令。但在本机从零搭集群时常见的是路径差异太大。例如在平台上用 hadoop 命令直接就可用而本机没有把$HADOOP_HOME/sbin加到 PATH导致你只能全路径调用。我建议在本机搭集群时把环境变量写进 /etc/profile.d/hadoop.shexport HADOOP_HOME/data/hadoop/hadoop-3.2.4 export JAVA_HOME/usr/lib/jvm/java-8-openjdk-amd64 export PATH$PATH:$HADOOP_HOME/bin:$HADOOP_HOME/sbin然后执行source /etc/profile.d/hadoop.sh。为什么放到 profile.d 而不是 ~/.bashrc因为 start-dfs.sh 在通过 SSH 远程启动 slave 节点时需要读取环境变量它不会加载你的用户 bashrc而会加载系统 profile。本机如果只有一个节点你往往感觉不到一旦你加上第二台 slaveslave 上找不到 JAVA_HOMEDataNode 永远启动不了。另外在线平台通常预装了 Docker 镜像。如果你在本地用 docker 镜像搭建 Hadoop 环境也要注意镜像里的 Hadoop 版本和宿主机端 Java 版本可能不一致导致客户端连接失败。我自己的做法是优先使用与集群版本一致的镜像不要在上面再装 OpenJDK 11因为 Hadoop 3.2 官方构建用的是 JDK 8换 JDK 11 后 HDFS 客户端会报 Unsupported class file major version。4. 核心功能实现上传、下载、目录管理的最小可运行代码4.1 上传文件create 与 append 的选择云盘上传分为两种场景。一种是新文件上传直接用 FileSystem.create() 创建目标文件。另一种是断点续传或追加内容用 append()。注意 HDFS 的 append 在 Hadoop 2.7 以后支持并发追加但并发写同一个文件会产生多个副本不一致的风险所以云盘系统我强烈建议新文件全部走 create追加场景走“先合并临时分块再一次性 create”。一个最小上传代码import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.fs.FileSystem; import org.apache.hadoop.fs.Path; import org.apache.hadoop.io.IOUtils; import java.io.BufferedInputStream; import java.io.FileInputStream; import java.io.OutputStream; public class HdfsUploader { public void upload(String localPath, String hdfsPath) throws Exception { Configuration conf new Configuration(); conf.set(fs.defaultFS, hdfs://hadoopmaster:9000); FileSystem fs FileSystem.get(conf); Path dst new Path(hdfsPath); OutputStream os null; BufferedInputStream is new BufferedInputStream(new FileInputStream(localPath)); try { os fs.create(dst, true); byte[] buffer new byte[1024 * 1024]; int len; while ((len is.read(buffer)) 0) { os.write(buffer, 0, len); } } finally { IOUtils.closeStream(os); IOUtils.closeStream(is); fs.close(); } } }逻辑说明fs.create() 第二个参数为 true 表示覆盖已存在文件这对应云盘里“同名文件是否覆盖”的业务开关。缓冲数组 1MB 是为了减少和 DataNode 的 RPC 调用次数如果你用很小比如 4KB 的缓冲上传 10MB 文件会发起 2500 多次写包网络拓扑那层容易触发超时重传。我用 IOUtils.closeStream 而不是直接调用 close是因为 HDFS 输出流在关闭时会做最后的数据同步如果中途失败直接 close 会抛出更难定位的异常而 IOUtils 内部会吞掉部分可忽略的异常把真正的错误信息留在最后。4.2 下载与校验FileSystem 与 LocalFile 系统别搞混下载代码和上传几乎对称最常踩的坑是 FileSystem.get() 拿到的是 LocalFileSystem 而非 DistributedFileSystem。如果你没有设置 fs.defaultFSHadoop 客户端默认认为你在操作本地文件这时 Path 里的 hdfs:// 会被当成本地路径解析下载就会报文件不存在。所以下载前可以先打印 fs 的 schemeFileSystem fs FileSystem.get(conf); System.out.println(filesystem scheme: fs.getScheme()); // 期望输出 hdfs如果是 file 说明配置没生效 Path src new Path(hdfsPath); FSDataInputStream in fs.open(src); int fileLen (int) fs.getFileStatus(src).getLen(); byte[] buffer new byte[4096]; try (FileOutputStream out new FileOutputStream(localPath)) { IOUtils.copyBytes(in, out, buffer.length, false); }参数说明这里强制把 in 和 out 交给 IOUtils.copyBytes第三个参数是 buffer 大小第四个参数是是否关闭流传 false 是因为我们还想在后续校验里继续使用 in其实这里不需要再用了但常见的错误是自动关闭后你再去调用 in.read() 会拿到一个 closed 的流。我的习惯是总是显式关闭把第四个参数设为 true但上面的写法是为了强调你可以在 finally 里统一关。校验文件完整性时建议对比 HDFS 上的文件长度和本地文件的长度如果长度一致基本不会有坏块因为 DataNode 在读取时会做 checksum 校验长度不一致就能暴露问题。4.3 元数据表设计文件路径与 block 映射HDFS 的 block 信息可以通过 FileStatus 获取但云盘业务不能每次都去问 NameNode所以元数据表要自己维护。最小表结构如下CREATE TABLE cloud_file ( file_id varchar(32) NOT NULL COMMENT 文件ID业务主键, file_name varchar(255) NOT NULL COMMENT 展示文件名, hdfs_path varchar(1024) NOT NULL COMMENT HDFS上的完整路径, file_size bigint NOT NULL DEFAULT 0, md5 varchar(32) DEFAULT NULL COMMENT 用于秒传, user_id int NOT NULL, parent_id varchar(32) DEFAULT NULL COMMENT 目录树父节点, status tinyint NOT NULL DEFAULT 0 COMMENT 0上传中,1可用,2删除, created_at datetime NOT NULL, PRIMARY KEY (file_id), KEY idx_user_parent (user_id, parent_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;这里 hdfs_path 不叫 path 而叫 hdfs_path是为了防止你混淆业务目录和物理存储路径。经验做法是 hdfs_path 统一为 /user/cloud/{user_id}/{file_id}前两级是 partition 一样的业务目录最后一级用 file_id 避免文件名冲突。这样设计的好处是删除文件只需要把 MySQL 状态置为 2并异步把 HDFS 文件移入回收站目录而不是立即删除防止用户后悔。4.4 秒传与断点续传Redis 缓存加临时目录秒传是云盘的标配。流程是客户端在上传前先发送文件 MD5 给 API 层API 层查 cloud_file 表如果存在相同 md5 且状态为 1则不执行上传直接给客户端返回“秒传成功”并把原文件路径作为新用户的 hdfs_path 记录。需要注意HDFS 要保证数据不丢失不能直接把路径共享出去最稳妥的做法是用 hadoop distcp 把原文件复制到新用户目录而不是创建硬链接。distcp 参数里有个名为 -update 的开关在秒传时不需要但做跨目录批量迁移时非常有用后面避坑章会详细讲。断点续传我这样实现每次上传分块时先在 Redis 里维护一个 key 为upload_progress:{fileId}的 hashfield 是分块序号value 是该分块在 HDFS 临时文件中的长度。上传完成后调用执行合并。如果某块失败客户端下次请求会带着已完成的块列表API 层对这些块跳过写入。这个方案的坑在于临时块太多会造成 NameNode 内存压力所以合并完成后必须立即删除临时文件并提示用户等待“合并中”状态完成千万不要在前端把合并回调漏掉。5. 遇到问题别慌Hadoop 云盘开发中常见的 5 个坑与排查命令5.1 坑 1小文件把 NameNode 内存打爆现象上传几百个几十 KB 的小文件后HDFS 状态页显示 NameNode 内存使用率持续上升集群出现 Full GCRPC 响应变慢。原因NameNode 在内存中为每个文件、每个块维护一条元数据记录默认每条记录约 150 字节但 JVM 对象头、链表等实际占用可能到 1KB。一个云盘如果有 10 万个 1KB 小文件元数据就是 10 万条记录堆内存直接吃紧。解决先停止向 HDFS 写小块。在写之前判断文件大小小于 10MB 的文件先合并成大文件比如按用户会话合并成一个压缩包然后 HDFS 只存压缩包元数据里保存原始文件清单。另外一个治标方案是增加 NameNode 堆内存export HADOOP_NAMENODE_OPTS-Xmx4g -Xms4g但堆内存不是无底洞4GB 对应大约 400 万块。我见过有人把 Xmx 调到 32G 但 GC 停顿从 1 秒变成 10 秒还不如合并小文件。最实际的排查命令是hdfs dfsadmin -report | grep Number of blocks观察块数量增长速率。如果块数量和文件数几乎 1:1说明每个小文件都独占一块你需要调整合并策略。5.2 坑 2上传时报 Too many open files现象并发上传 50 个文件时API 层日志报java.io.IOException: Too many open files但文件数远没到系统限制。原因每个 FileSystem.get(conf) 都会创建新的客户端连接每个上传流会占用一个文件描述符。如果程序用完后没有关闭 FileSystem 或 OutputStreamLinux 默认的 1024 软限制瞬间被打满。解决把 FileSystem 实例做成单例用完后不关因为重新获取会重新缓存真正要关的是每个上传的输出流。排查当前进程打开文件数lsof -p pid | wc -l ulimit -n如果是软限制不够可以临时调大ulimit -n 65535但治本还是在代码里用 try-with-resources 保证每个流都关闭。还有一个隐蔽点Hadoop 的 DFSClient 内部有缓存池明确调用 fs.delete() 删除临时文件时文件句柄会延迟释放所以删除后可以调用一次 FileSystem.clearStatistics() 观察是否释放。5.3 坑 3DataNode 启动失败磁盘空间不足误判现象DataNode 正常启动但几秒后自动退出日志里有 Failed to initialize提示磁盘空间不足但 df -h 显示磁盘还有 100GB。原因Hadoop 认为的最小存储空间是dfs.datanode.du.reserved默认值是 0但有些发行版会把 data.dir 和一个系统保留分区放一起DataNode 初始化时检查到的是整个分区剩余空间而分区实际已经满了或 inode 耗尽。另一个原因是 data.dir 目录权限不对DataNode 进程是 hdfs 用户而目录属主是 root写入失败会误报为磁盘不足。解决先看确认目录挂载点mount | grep /data df -h /data/hadoop/datanode然后把 datanode 目录权限改成 hdfs 用户chown -R hdfs:hdfs /data/hadoop/datanode。如果确实是保留空间太小就把 reserved 调小property namedfs.datanode.du.reserved/name value10g/value /property推荐保留 10GB 而不是 0否则磁盘写满后整个集群会进入只读状态云盘用户删除文件都执行不了。5.4 坑 4distcp 跨目录复制参数用错导致任务失败现象用 distcp 把用户 A 的文件复制到用户 B 目录命令返回 success 但目标目录为空或者复制到一半抛出 FileNotFoundException。原因distcp 是以 MapReduce 作业方式运行的默认会把源路径下的隐藏临时文件也列进来。如果源路径包含正在写入的临时文件或者复制时目标路径已有同名文件且没有加 -update 参数就会跳过或报错。解决我常用的完整命令是hadoop distcp -update -skipcrccheck -m 4 hdfs://hadoopmaster:9000/user/cloud/user_a hdfs://hadoopmaster:9000/user/cloud/user_b参数说明-update 表示只覆盖差异文件-skipcrccheck 跳过 CRC 校验以提升速度-m 4 控制并发 map 任务数。如果你的云盘单机空间不足distcp 会出现跨节点复制时目标 DataNode 存不下块表现为任务一直重试。此时应该先检查目标目录所在 DataNode 的剩余空间用hdfs dfsadmin -report看每个 DataNode 的 Available 字段。5.5 坑 5权限认证 LocalFileSystem 与 HDFS 混淆现象代码里明明调用 fs.rename()结果在本地目录多出一个同名文件而 HDFS 上没有变化。原因Configuration 没有加载 core-site.xmlFileSystem.get() 默认返回 LocalFileSystem所有 Path 都是本地文件。这在单元测试里最常见因为 IDE 的工作目录没有把 Hadoop 配置目录放进 classpath。解决在代码里显式加载资源Configuration conf new Configuration(); conf.addResource(new Path(/data/hadoop/etc/hadoop/core-site.xml)); conf.addResource(new Path(/data/hadoop/etc/hadoop/hdfs-site.xml));或者更稳妥地在调用 FileSystem.get 前检查 scheme。我在排查时喜欢用一个一行命令hdfs dfs -ls /user/cloud如果这个命令能看到进程里上传的文件说明集群是通的如果看不到但你 Java 代码上传后本地有文件那就是上面的问题了。做项目时把配置文件路径放到一个常量类里统一维护比在每段代码里硬编码要省心得多。6. 最后一步用并发上传压力测试验证你的云盘到底能撑多大6.1 测试脚本模拟并发上传系统写完最终要回答“能撑多大并发”这个问题。我不用 JMeter因为云盘的瓶颈往往在 HDFS 写入吞吐而不是 HTTP 层。写个简单的 Shell 脚本并发跑 10 个上传任务for i in $(seq 1 10) do java -jar clouddisk-client.jar upload /data/test/testfile_$i.bin /user/cloud/test_$i.bin done wait echo all upload done同时观察三个指标API 层耗时、NameNode 的 RPC 延迟、DataNode 的写 IO。用小工具监控hdfs dfsadmin -report | grep Last contact iostat -x 1 56.2 观察指标与调优方向如果同时上传很多文件会出现 NameNode RPC 排队。调优方向是提高 handle 数、增大批量提交以及设置 dfs.namenode.handler.count 为 100 以上。另外上传要设置合理的 blocksize大文件可以调高小文件使用合并策略。看 iostat 如果 util 接近 100%说明写磁盘饱和此时加网络并发也没用得提升磁盘性能或增加副本。6.3 我的教训先测元数据再测磁盘我做过一个项目直接填充了 10 万个文件结果 NameNode 内存告警后来才意识到应该先做元数据压测。先往 MySQL 模拟插入 10 万条记录测查询性能再往 HDFS 上传大量小文件看内存增长。这样分开测能够快速定位瓶颈到底在哪一层。最后一个教训把临时文件回收任务放在 Spring 的定时器里每 5 分钟清理一次清理时总是删除当天目录结果用户中途上传被误删。后来我改成只清理创建时间超过 30 分钟的目录并且清理前先检查该文件是否处于“上传中”状态。希望这些经验能在你交项目前夕给你一点信心真遇到报错就把日志贴给搜索引擎再不行从头梳理一遍临时目录和心跳超时大概率能找到问题。希望帮到你。本文还有配套的精品资源点击获取
返回列表