
简介这是一份面向计算机相关专业学生与开发者的Hadoop好友推荐系统完整项目源码适合用作毕业设计、课程设计、作业提交或项目初期立项演示也可供初学者进阶学习。项目已通过导师指导与答辩评审获得95分成绩代码经过实际运行测试功能完整可用。压缩包共约2000个文件整体79.5MB涵盖Java源码、JSP页面、CSS样式、JS脚本、XML配置、properties配置及jar依赖等另含大量png、gif图片资源与class编译文件构成一套结构完整的Web工程。系统围绕Hadoop分布式计算实现好友推荐核心逻辑涉及距离计算、聚类分析与数据映射等模块并附有部署文档与全部资料便于快速搭建运行环境。已有162人学习关注读者可据此掌握Hadoop在实际推荐场景中的应用思路也可在现有代码基础上二次开发扩展更多功能。1. 从一份 95 分毕设拆开看Hadoop 好友推荐系统到底交付了什么答辩评审 95 分、导师认可、代码实测跑通——这几个标签放在一起说明这份资源不是那种「能编译但跑不出结果」的半成品。我拿到包之后第一件事不是看文档而是先翻 class 文件清单HUtils、CloudAction、DBService、Utils、DrawPic、BaseDAOImpl再加上ClusterDataMapper、DeltaDistanceMapper、CalDistanceMapper、FindInitDCMapper四个 Mapper。这个结构一眼就能看出是典型的 Hadoop MapReduce 离线计算链路配合 JDBC 做数据落地最后用DrawPic出可视化结果。它解决的核心问题很具体给定一批用户好友关系数据通过 MapReduce 计算用户之间的相似度共同好友数、Jaccard 系数等再按阈值筛选出「可能认识的人」推荐列表。适合计算机相关专业的毕设、课设场景也适合想跑通一个完整 Hadoop 离线计算链路的新手——因为它的 Mapper 职责拆分得很清楚不是一坨代码糊在一起。下面我按「数据怎么流 → 环境怎么搭 → 代码怎么读 → 坑在哪」的顺序拆一遍。2. 四个 Mapper 的数据流从好友关系到推荐列表的完整链路2.1 为什么是四段式 MapReduce 而不是一段很多人第一反应是好友推荐不就是算两两之间的共同好友数吗一个 MapReduce 搞定理论上可以但实际数据量上去之后会翻车。假设有 N 个用户两两组合是 N² 级别如果每个用户平均 50 个好友光中间 shuffle 的数据量就能把单机内存打爆。这份代码拆成四段是有道理的Mapper/类职责输入输出FindInitDCMapper找初始直接好友对原始好友关系好友对 标记CalDistanceMapper计算好友对距离好友对距离值DeltaDistanceMapper增量距离修正上轮结果 新增修正后距离ClusterDataMapper聚类分组距离矩阵簇标签FindInitDCMapper负责把原始关系里「已经是好友」的对找出来作为初始种子。CalDistanceMapper才是真正算相似度的核心。DeltaDistanceMapper处理增量场景——当有新用户加入时不需要全量重算只算增量部分。ClusterDataMapper最后做聚类把相似度高的用户归到一组。提示如果你的数据量不大比如几千个用户其实可以跳过 DeltaDistanceMapper直接全量跑 CalDistanceMapper。但毕设答辩时老师大概率会问「数据量大了怎么办」这时候增量计算就是加分项。2.2 相似度计算的核心逻辑与参数CalDistanceMapper里最常见的实现是 Jaccard 相似度两个用户的共同好友数除以他们的好友并集大小。公式很朴素但工程上有几个参数必须搞清楚。// CalDistanceMapper 核心逻辑示意 public class CalDistanceMapper extends MapperLongWritable, Text, Text, Text { // 阈值相似度低于此值不输出减少下游压力 private static final double SIMILARITY_THRESHOLD 0.3; Override protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException { // 输入格式userId \t friend1,friend2,friend3 String[] parts value.toString().split(\t); String userId parts[0]; String[] friends parts[1].split(,); // 对每个好友输出 好友ID, 当前用户ID 的倒排 for (String friend : friends) { // 用 friend:userId 作为 key保证同一好友对落到同一 reducer context.write(new Text(friend), new Text(userId)); } } }这段代码的关键在于输出 key 的设计。用好友 ID 作为 key同一个好友的所有关注者会落到同一个 Reducer这样在 Reduce 阶段就能两两组合算出共同好友数。SIMILARITY_THRESHOLD这个参数直接决定推荐列表的长度——设太低会推荐一堆不相关的人设太高又推不出几个。我一般建议从 0.3 开始试根据结果密度调整。DeltaDistanceMapper的逻辑类似但它多了一个「版本号」或「时间戳」字段只处理比上次计算更新的数据。这个设计在真实社交场景里很常见因为用户关系是持续变化的不可能每次都全量重算。2.3 数据落地与可视化DBService 和 DrawPic 怎么配合MapReduce 跑完的结果默认是文本文件但这份代码通过DBService和BaseDAOImpl把结果写进了数据库。BaseDAOImpl是典型的 DAO 模式实现封装了 JDBC 的连接、查询、更新操作。DBService则是对外暴露的服务层负责协调 Mapper 输出和数据库写入。// DBService 中结果落地的典型写法 public class DBService { private BaseDAOImpl dao new BaseDAOImpl(); public void saveRecommendResult(String userId, ListString recommendedUsers) { // 先清理该用户的历史推荐避免重复累积 dao.executeUpdate(DELETE FROM recommend WHERE user_id ?, userId); // 批量插入新推荐结果 for (String recUser : recommendedUsers) { dao.executeUpdate( INSERT INTO recommend(user_id, rec_user_id, score) VALUES(?,?,?), userId, recUser, calculateScore(userId, recUser) ); } } }DrawPic类负责最后一步可视化通常是用 JFreeChart 或者直接生成 HTML 表格。如果你的答辩需要展示「推荐效果」这个类生成的图表就是最直观的材料。注意数据库连接信息一般写在配置文件里不要硬编码在 Java 代码中。我见过太多毕设把密码写死在DBService里答辩时被老师一眼看穿。3. 从零把环境跑起来伪分布式搭建与 IDEA 远程提交3.1 Hadoop 伪分布式环境的关键配置项这份代码要在 Hadoop 上跑第一步是把环境搭起来。伪分布式是最适合毕设场景的——单机模拟多节点资源占用可控。核心改三个文件core-site.xml、hdfs-site.xml、mapred-site.xml。!-- core-site.xml指定 HDFS 的 NameNode 地址 -- configuration property namefs.defaultFS/name valuehdfs://localhost:9000/value /property property namehadoop.tmp.dir/name value/usr/local/hadoop/tmp/value /property /configurationfs.defaultFS告诉客户端 NameNode 在哪里hadoop.tmp.dir是临时目录不配的话默认在/tmp下重启机器数据就没了。hdfs-site.xml里主要配dfs.replication伪分布式设为 1 就行因为只有一个 DataNode。!-- hdfs-site.xml副本数设为 1 -- configuration property namedfs.replication/name value1/value /property /configurationmapred-site.xml要指定用 YARN 跑 MapReduceconfiguration property namemapreduce.framework.name/name valueyarn/value /property /configuration配完之后执行hdfs namenode -format格式化再start-dfs.sh和start-yarn.sh启动。用jps检查进程应该能看到 NameNode、DataNode、ResourceManager、NodeManager 四个进程。3.2 Windows 下用 IDEA 远程提交任务的完整步骤很多人的开发环境是 Windows但 Hadoop 跑在 Linux 虚拟机上。这种跨环境提交任务有几个必须注意的点。第一步在 IDEA 里创建一个 Maven 项目把 Hadoop 客户端依赖加进去dependency groupIdorg.apache.hadoop/groupId artifactIdhadoop-client/artifactId version3.3.4/version /dependency版本号要和你服务端装的 Hadoop 版本一致否则会出现各种序列化不兼容的玄学问题。第二步把 Linux 上的core-site.xml和hdfs-site.xml复制到 Windows 项目的resources目录下。这样客户端才知道连哪个 NameNode。第三步配置winutils.exe。Windows 上跑 Hadoop 客户端需要这个工具来模拟文件权限操作没有它会报Could not locate executable null\bin\winutils.exe。下载对应版本的 winutils放到一个目录下然后设置环境变量HADOOP_HOME指向该目录。# 在 Windows 环境变量中添加 HADOOP_HOME D:\hadoop-3.3.4 # 并把 %HADOOP_HOME%\bin 加入 PATH第四步在代码里指定用户身份避免权限异常// 提交任务前设置避免 Windows 用户名导致的权限问题 System.setProperty(HADOOP_USER_NAME, root); Configuration conf new Configuration(); Job job Job.getInstance(conf, friend-recommend); job.setJarByClass(FriendRecommendDriver.class); job.setMapperClass(CalDistanceMapper.class); job.setReducerClass(CalDistanceReducer.class); job.setOutputKeyClass(Text.class); job.setOutputValueClass(Text.class); FileInputFormat.addInputPath(job, new Path(/input/friends)); FileOutputFormat.setOutputPath(job, new Path(/output/recommend)); System.exit(job.waitForCompletion(true) ? 0 : 1);HADOOP_USER_NAME这个设置是血泪经验——不设的话Windows 用户名会被当成 HDFS 操作者而 HDFS 上通常没有这个用户目录直接报权限拒绝。3.3 输入数据格式与运行验证输入数据放在 HDFS 的/input/friends目录下格式是每行一个用户及其好友列表用制表符分隔user001 friendA,friendB,friendC user002 friendB,friendC,friendD user003 friendA,friendD,friendE跑完之后用hdfs dfs -cat /output/recommend/part-r-00000查看结果。如果输出为空先检查输入路径是否正确、Mapper 输出 key 是否合理、Reducer 里有没有过滤条件把数据全滤掉了。4. 避坑与排查这份代码最容易翻车的五个地方4.1 现象任务提交后卡在 map 0% reduce 0%原因通常是 YARN 资源不够。伪分布式环境下yarn.nodemanager.resource.memory-mb默认可能是 8192但你的虚拟机只给了 4G 内存容器申请不到资源就一直等。解决在yarn-site.xml里把内存调小同时调整mapreduce.map.memory.mb和mapreduce.reduce.memory.mbproperty nameyarn.nodemanager.resource.memory-mb/name value2048/value /property property namemapreduce.map.memory.mb/name value512/value /property property namemapreduce.reduce.memory.mb/name value512/value /property4.2 现象ClassNotFoundException 或 NoSuchMethodError原因几乎都是依赖版本冲突。Hadoop 客户端版本和服务端不一致或者项目里混入了多个版本的 hadoop-common。解决用mvn dependency:tree检查依赖树把冲突的排除掉。最稳妥的做法是只保留hadoop-client一个依赖让它自己管理传递依赖。4.3 现象中文乱码原因输入文件编码不是 UTF-8或者 Java 编译时没指定编码。解决确保输入文件是 UTF-8 无 BOM 格式IDEA 里设置 File Encoding 为 UTF-8Maven 编译插件加encodingUTF-8/encoding。4.4 现象Reducer 输出结果重复原因CalDistanceMapper里对同一对好友输出了两次A→B 和 B→AReducer 没有去重。解决在 Mapper 输出时对 userId 和 friendId 做排序保证 A-B 和 B-A 输出相同的 key。或者在 Reducer 里用 Set 去重。4.5 现象DrawPic 生成的图表空白原因JFreeChart 在无头环境下需要设置 headless 模式否则抛异常但不中断程序。解决在调用绘图前加一行System.setProperty(java.awt.headless, true);5. 进阶玩法把推荐结果做成可交互的查询接口跑通离线计算只是第一步。答辩时如果老师问「这个推荐结果怎么用」你总不能说「看文本文件」吧。我一般会在这个项目基础上加一层轻量查询接口把DBService里的推荐结果暴露出去。具体做法是用 Java 自带的HttpServer不需要额外容器起一个服务// 轻量 HTTP 接口把推荐结果暴露出去 HttpServer server HttpServer.create(new InetSocketAddress(8080), 0); server.createContext(/recommend, exchange - { String query exchange.getRequestURI().getQuery(); String userId query.split()[1]; // 从数据库查该用户的推荐列表 ListString recs dbService.getRecommend(userId); String response String.join(,, recs); exchange.sendResponseHeaders(200, response.getBytes().length); exchange.getResponseBody().write(response.getBytes()); exchange.close(); }); server.start();这样访问http://localhost:8080/recommend?userIduser001就能拿到推荐列表。对于毕设演示来说这个交互感比翻文本文件强太多。验证推荐效果时我习惯用「命中率」这个指标随机抽 100 个用户看推荐列表里有多少比例确实是他们已有的好友但不在直接好友列表里。如果命中率低于 10%说明阈值设得太高或者相似度算法需要调整。还有一个容易被忽略的点DeltaDistanceMapper的增量逻辑需要维护一个「上次计算时间」的状态。常见做法是在 HDFS 上存一个last_run_timestamp文件每次任务启动时读取结束后更新。这个细节在答辩时如果被问到「增量怎么实现的」能答上来就是加分项。从那以后我每次拿到这类 Hadoop 项目都强制先跑一遍最小数据集验证链路再上全量数据。很多翻车不是代码逻辑错而是环境配置和数据格式的细节没对齐。希望帮到你。本文还有配套的精品资源点击获取