
简介推荐系统是大数据与机器学习领域的高频应用场景而协同过滤作为其中最经典的算法思想在电商、视频、资讯等平台中广泛落地。当数据规模增长到单机无法高效处理时分布式计算框架便成为关键支撑。Hadoop凭借其HDFS分布式存储与MapReduce并行计算模型为离线推荐场景提供了稳定可靠的技术方案尤其适合处理用户-物品评分矩阵的批量计算。在工程实践中完整链路涉及数据清洗、评分矩阵构建、物品同现统计、相似度计算与TopN推荐生成等多个阶段并需结合MySQL完成结果存储与查询展示从而形成一套从数据到服务的闭环。本文以电影推荐场景为例基于MovieLens公开数据集系统拆解一个基于Hadoop的推荐系统设计思路与落地全流程从算法公式推导、MapReduce多阶段实现到集群部署与常见问题排查为课程设计、毕业设计及大数据入门实践提供完整参考。 毕业设计这东西选对题目等于成功了一半。今天拿一个热门到不能再热的题目来拆基于 Hadoop 实现的电影推荐系统附源码加数据库整套。我相信很多人在选题时都会瞄到类似的标题但真正动手时才发现——结构清晰、能跑通、能复现、还能写进论文的完整项目远比想象中少。这篇文章我就把这个题目从设计到落地全流程拆开讲包括为什么这么设计、数据从哪来、核心算法怎么用 MapReduce 一步步实现、线上跑起来会遇到哪些坑以及数据库在整个项目里到底扮演什么角色。这篇内容适合三类人看一是正在准备 Hadoop 课程设计或毕业设计的在校学生二是想搞懂推荐系统落地流程的初学者三是想在简历上写一个完整大数据项目的求职者。我尽量按真实开发场景来讲不整虚的。1. 项目整体设计与技术选型1.1 核心需求解析毕业设计不是“造产品”先捋清楚这个项目到底要交付什么。标题写得很明确源码 数据库意思是最终要有完整的代码工程、可运行的程序、可导入的 SQL 脚本。但“推荐系统”四个字才是真正的灵魂——它必须实现推荐逻辑本身而不仅仅是一个 Hadoop 环境的 Hello World。所以在动工之前我心里要过一遍几个核心问题数据从哪来用什么数据集推荐算法选什么为什么Hadoop 在其中承担什么角色哪些计算必须在 Hadoop 上做数据库存什么怎么和 Hadoop 配合前端/接口怎么展示推荐结果这五个问题是这个题目的骨架。很多同学做这个题时最大的误区是把算法写得天花乱坠却忽略了系统完整性反过来也有只做了数据导入导出、没有任何推荐逻辑的“假系统”。一个合格的毕业设计需要在算法深度和系统完整度之间找到平衡点。我的建议是算法环节用协同过滤做核心Hadoop 负责离线计算MySQL 存业务数据后端做查询展示。这套组合既能在论文里讲清理论又能写出实打实的 MapReduce 代码还能做出可见的运行效果。1.2 技术选型为什么是 Hadoop 而非 Spark很多人在选型时会纠结现在 Spark 这么火为什么不直接用 Spark甚至用 Python 单机跑个 sklearn 不是更简单吗答案在于题目本身的定位。题目明确写了 Hadoop因此技术框架是定死的。但更深层的原因是Hadoop 的 MapReduce 模型虽然笨重却非常适合展示分布式计算的核心思想分而治之。推荐系统中“用户-物品”矩阵的计算天然可以拆成多个 Map-Reduce 阶段每一个阶段都对应一个清晰的业务逻辑。用 MapReduce 去实现协同过滤能让人真切感受到“数据并行计算”是怎么一回事。Hadoop 生态内我也做了具体选型存储层HDFS 存原始评分数据和中间计算结果计算层MapReduce 实现协同过滤各阶段业务层MySQL 存储用户表、电影元数据表和最终推荐结果表调度层不引入 YARN 的高级调度器直接用默认 FIFO 调度即可这里我特意不引入 Hive、Spark、Flume 这些组件理由很简单课程设计和本科毕设的时间根本不够再把一套完整生态吃透而且引入太多组件会让论文的重点失焦。项目核心是推荐算法的分布式实现不是大数据平台搭建。1.3 整体架构与数据流整个系统的数据流转是这样设计的原始数据 → 数据清洗 → HDFS → 多阶段 MapReduce 计算 → 推荐结果表HDFS→ 导入 MySQL → WEB/API 查询展示画成模块图大概是这样数据源模块MovieLens 评分数据集包含用户 ID、电影 ID、评分值、时间戳预处理模块清洗评分数据过滤无效记录格式化存入 HDFS离线推荐模块四个连续的 MapReduce 作业完成相似度计算和推荐生成结果存储模块将 HDFS 上的推荐结果通过 Sqoop 或直接解析导出到 MySQL展示模块提供一个简单的 Spring Boot 后端接口查询某用户的 TopN 推荐列表这个架构非常“教科书”但也很实用。它让整个项目有了清晰的层次论文里能画架构图答辩时能讲清楚每个模块在干嘛。顺序也很关键——Hadoop 离线计算是核心MySQL 只做最终结果存储和查询不要把复杂的计算硬塞进关系型数据库里。2. 数据准备与预处理2.1 数据集选择千万不要自己编数据做推荐系统数据是决定成败的第一步。我见过不少同学想省事自己手写几十条评分数据“意思一下”。结果就是算法跑起来跟没跑一样推荐结果毫无意义论文里也没法做任何效果分析。我推荐用MovieLens 数据集这是明尼苏达大学 GroupLens 研究组公开的经典推荐系统数据集。它有多个规模版本毕业设计选ml-latest-small约 600 用户、9000 部电影、10 万条评分或ml-1m约 4000 用户、6000 部电影、100 万条评分都非常合适。选 ml-latest-small 的好处是数据规模适中MapReduce 跑起来不会太慢又能体现出分布式计算的价值。数据文件也很规整userId,movieId,rating,timestamp 1,1,4.0,964982703 1,3,4.0,964981247 1,6,4.0,964982224 ...movies.csv是电影元数据movieId,title,genres 1,Toy Story (1995),Adventure|Animation|Children|Comedy|Fantasy 2,Jumanji (1995),Adventure|Children|Fantasy ...前者是推荐算法的主输入后者可以导入 MySQL 用于展示结果时回查电影标题。这样一来数据就有两个去处评分数据进 HDFS电影元数据进 MySQL天然形成双轨数据流。2.2 数据清洗哪些记录必须过滤原始数据集虽然已经很规整但直接丢进 Hadoop 还是会出问题。我总结了三类必须处理的脏数据第一类是缺失字段的记录。MovieLens 大面积数据是完整的但偶尔会有空行或异常短的行。处理方式是写一个简单的 MapReduce 预处理作业过滤掉字段数量不满足要求的行。第二类是无效评分。评分值必须在 1-5 之间如果出现 0 分或负数不能直接删掉——要调查原因但实际处理中直接过滤即可。第三类是冷启动用户/物品。有些用户只有一条评分记录有些电影只被一个用户评分过。这类数据在计算相似度时会产生大量无意义的稀疏项建议设置阈值评分记录少于 5 条的用户直接过滤被评分少于 5 次的电影也过滤掉。注意一个细节脏数据比例。不能凭感觉觉得“差不多干净”要用预处理作业的 Counter 统计各类过滤记录的数量跑完看一眼19/01/01 10:00:00 INFO mapreduce.Job: Map-Reduce Framework Filtered Records: 825 Invalid Rating: 12这样论文里就能写“经过数据清洗共过滤无效记录 825 条有效评分数据保留率为 99.2%”既真实又专业。2.3 数据库表结构与 HDFS 存储规划先说 MySQL 这边核心是四张表CREATE TABLE t_user ( user_id int NOT NULL, username varchar(64) DEFAULT NULL, PRIMARY KEY (user_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE t_movie ( movie_id int NOT NULL, title varchar(255) NOT NULL, genres varchar(255) DEFAULT NULL, PRIMARY KEY (movie_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE t_rating ( rating_id bigint NOT NULL AUTO_INCREMENT, user_id int NOT NULL, movie_id int NOT NULL, rating double NOT NULL, create_time timestamp NULL DEFAULT NULL, PRIMARY KEY (rating_id), KEY idx_user_id (user_id), KEY idx_movie_id (movie_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE t_recommendation ( id bigint NOT NULL AUTO_INCREMENT, user_id int NOT NULL, movie_id int NOT NULL, score double NOT NULL, rank int NOT NULL, PRIMARY KEY (id), KEY idx_user_id (user_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;这里t_rating其实不是必须的因为在 Hadoop 里算完推荐后原始评分数据在 MySQL 里已经没有业务价值。但为什么要建我在论文里解释为“作为原始数据的备份存储用于数据管理与查询验证”也是合理的设计。HDFS 这边的目录规划也要提前想好/user/hadoop/movie/ ├── input/ # 原始数据存放目录 ├── stage1/ # 阶段一输出用户-物品评分矩阵 ├── stage2/ # 阶段二输出物品共现矩阵 ├── stage3/ # 阶段三输出相似度矩阵 ├── stage4/ # 阶段四输出最终推荐结果 └── data_cache/ # 小文件缓存可选每个阶段的输出都独立目录方便排查问题和调试。这是我在实际项目中养成的习惯——MapReduce 作业链很长时如果所有中间结果都挤在一个目录里出了 bug 根本没法查。3. 推荐算法核心实现3.1 算法选型为什么是 Item-Based 协同过滤推荐系统的算法选择有很多基于用户的协同过滤UserCF、基于物品的协同过滤ItemCF、矩阵分解、深度学习模型等。对 Hadoop 课程设计来说基于物品的协同过滤ItemCF是最合适的选择原因有几点一是业务解释性强。电影推荐场景中用户更希望看到“和你喜欢的电影相似的作品”而不是“和你兴趣一样的人看的作品”ItemCF 的结果容易解释论文里好写答辩时好讲。二是计算模式更适合 MapReduce。ItemCF 的核心是计算物品间相似度可以拆成“同现矩阵 余弦相似度”的一步步 MapReduce 作业。每一步都是独立的任务展示起来非常清晰。三是效果足够。MovieLens 数据集上ItemCF 的效果在中型数据集上已经相当不错完全不输给更复杂的方法。3.2 核心公式余弦相似度与加权评分推荐流程分为两部分离线相似度计算和在线评分预测。离线就是 Hadoop 里跑的在线则是从 MySQL 里查推荐结果。先说相似度计算。最常用的是余弦相似度公式是similarity(i, j) sum(u∈U) (R_ui * R_uj) / (sqrt(sum(u∈U) R_ui^2) * sqrt(sum(u∈U) R_uj^2))其中 R_ui 表示用户 u 对物品 i 的评分。要按这个公式算需要先统计出物品-物品共现矩阵也就是“每个用户同时评价过物品 i 和物品 j 的次数/评分和”。这一步正好是 MapReduce 最擅长的聚合操作。然后是推荐生成。对用户 u候选物品 p 的预测评分pred(u, p) sum(i∈N(u)) sim(i, p) * R_ui / sum(i∈N(u)) |sim(i, p)|其中 N(u) 是用户 u 评过分的物品集合sim(i, p) 是物品 i 和物品 p 的相似度。取 TopN 作为推荐结果。这两个公式在论文里一定要推导一遍因为它是整个推荐系统的“算法贡献点”。而在编码实现时公式要转换成一步步 MapReduce 就能算的形式。3.3 MapReduce 多阶段实现拆解整个离线计算我拆成了四个连续的 Job每段代码的量级都在百行以内适合课程设计时手写也方便在论文里逐个讲解。3.3.1 阶段一构建评分矩阵输入是清洗后的评分数据一行一条格式为userId,movieId,rating。这个阶段的目标是把数据转换成“以用户为行、物品为列的评分向量”虽然没有真正的稠密矩阵但逻辑上需要这个视图。阶段一的 Mapper 很简单public static class RatingMapper extends MapperObject, Text, Text, Text { private Text outKey new Text(); private Text outValue new Text(); Override protected void map(Object key, Text value, Context context) throws IOException, InterruptedException { String[] fields value.toString().split(,); if (fields.length ! 3) { return; } String userId fields[0]; String movieId fields[1]; String rating fields[2]; outKey.set(userId); outValue.set(movieId : rating); context.write(outKey, outValue); } }Reducer 就把同一个用户的所有评分拼成一个向量public static class RatingReducer extends ReducerText, Text, Text, Text { private Text outValue new Text(); Override protected void reduce(Text key, IterableText values, Context context) throws IOException, InterruptedException { StringBuilder sb new StringBuilder(); for (Text value : values) { if (sb.length() 0) { sb.append(,); } sb.append(value.toString()); } outValue.set(sb.toString()); context.write(key, outValue); } }这样每个用户对应一行u1 m1:4,m2:5,m3:3。这个输出看起来简单但它是后续所有计算的基础。3.3.2 阶段二物品同现矩阵统计这个阶段要计算“哪个物品和哪个物品同时被同一个用户评分”。做法是把阶段一的输出读进来同一个用户向量内所有物品两两组合每组合出现一次就计数 1。两两组合的痛点在于数据量会爆炸。用户有 k 个评分会产生 k*(k-1) 个组合。所以在这个阶段一定要控制用户评分数量的上限——预处理阶段过滤超高频用户是合理的否则一个疯狂看电影的用户会拖垮整个 Job。Mapper 的伪代码public static class CooccurrenceMapper extends MapperText, Text, Text, IntWritable { private Text outKey new Text(); private IntWritable outValue new IntWritable(1); Override protected void map(Text key, Text value, Context context) throws IOException, InterruptedException { String[] items value.toString().split(,); for (int i 0; i items.length; i) { String itemI items[i].split(:)[0]; for (int j i 1; j items.length; j) { String itemJ items[j].split(:)[0]; if (itemI.compareTo(itemJ) 0) { outKey.set(itemI : itemJ); } else { outKey.set(itemJ : itemI); } context.write(outKey, outValue); } } } }这里的小技巧是对物品对做排序约定保证m1:m2和m2:m1永远被规范化成同一种 key否则最终相似度会重复计算结果直接翻倍。这是我在第一次实现时踩过的坑后来专门加了这个排序逻辑。Reducer 就是简单的累加public static class CooccurrenceReducer extends ReducerText, IntWritable, Text, DoubleWritable { Override protected void reduce(Text key, IterableIntWritable values, Context context) throws IOException, InterruptedException { int sum 0; for (IntWritable value : values) { sum value.get(); } context.write(key, new DoubleWritable(sum)); } }3.3.3 阶段三相似度计算有了同现矩阵还不够因为共现次数只是加权的基础更准确的相似度要结合物品总被评次数做归一化。最简单的办法是在阶段二完成后再算一个物品对相似度用共现次数除以两个物品各自被评次数的乘积的平方根。这一步我仍然用 MapReduce 来做。Mapper 读入阶段二输出物品i:物品j, 共现次数同时读入一个侧数据文件每个物品整个集合中被评分的次数Reducer 拿到两侧数据后直接套余弦公式。侧数据文件从哪来阶段一还能顺便统计出每个物品被多少个用户评分过。把这条链串起来就是论文里写的“多阶段级联式 MapReduce 作业流”。3.3.4 阶段四TopN 推荐生成最后一个阶段对每个用户把该用户评分过的所有物品和候选相似物品做加权累加生成预测评分后排序取 TopN。这个阶段的 Mapper 逻辑是输入有两类一类是阶段三的相似度表一类是阶段一的用户评分向量。需要用 DistributedCache 把相似度表分发到所有节点然后 Mapper 读用户评分向量在 setup 阶段读取相似度表对每个候选物品计算加权得分。Reducer 负责对每个用户的所有候选物品按得分排序取前 N 个输出。输出格式设计为userId movieId:score,movieId:score,...这样每一行就是该用户的 TopN 推荐结果后续导出 MySQL 时解析也很方便。这四个阶段串成一个作业链在 Driver 类里依次提交Configuration conf new Configuration(); Job job1 Job.getInstance(conf, stage1-build-rating-matrix); // ... 设置 Mapper/Reducer/Input/Output ... job1.waitForCompletion(true); Job job2 Job.getInstance(conf, stage2-item-cooccurrence); // ... 类似设置 ... job2.waitForCompletion(true); // 依次执行 job3、job4这里的 waitForCompletion 串行执行虽然慢但保证每个阶段的结果立即可见调试时能随时检查中间结果。这是我在实际开发中踩了无数坑之后总结出的最佳实践——想并行优化是后话先确保流程可靠再谈性能。3.4 项目目录结构与源码简析源码结构上我用的是标准 Maven 工程包名按模块划分。整个工程放在 GitHub 上核心类不超过 15 个非常适合答辩时讲清楚。movie-recommend-system/ ├── pom.xml ├── src/ │ ├── main/ │ │ ├── java/ │ │ │ ├── com/recommend/RecommenderDriver.java # 作业链驱动 │ │ │ ├── com/recommend/stage1/RatingMatrix.java │ │ │ ├── com/recommend/stage2/Cooccurrence.java │ │ │ ├── com/recommend/stage3/Similarity.java │ │ │ └── com/recommend/stage4/RecommendGenerator.java │ │ └── resources/ │ │ └── log4j.properties │ └── test/ └── sql/ ├── init.sql # 数据库建库建表脚本 └── data.sql # 电影元数据导入脚本Maven 的 pom 里需要引入 hadoop-client 依赖版本要和集群一致。这里要特别提醒如果 Hadoop 是 3.xJDK 必须是 8不要用 Hadoop 2.x 的依赖去跑 3.x 的集群会报各种奇怪的 Java 版本错误。4. 部署与实操记录4.1 Hadoop 伪分布式环境准备现在到了真正动手阶段。你的环境至少需要一台 Linux 虚拟机或云主机配置不用太高4G 内存 2 核就够用。我自己的实操环境是 Ubuntu 20.04 Hadoop 3.2.4 JDK 1.8这套组合最稳定。环境准备步骤依次是安装 JDK配置 JAVA_HOME下载 Hadoop 安装包解压配置 core-site.xml、hdfs-site.xml、mapred-site.xml、yarn-site.xml配置 SSH 免密登录伪分布式也要配不然 start-dfs.sh 会卡住格式化 NameNode启动 HDFS 和 YARN这里配置mapred-site.xml时我建议把内存参数调大一些尤其是 MapReduce 跑多个阶段时默认的 1G 内存很容易让 Container 挂掉configuration property namemapreduce.map.memory.mb/name value1024/value /property property namemapreduce.reduce.memory.mb/name value2048/value /property property namemapreduce.map.java.opts/name value-Xmx800m/value /property property namemapreduce.reduce.java.opts/name value-Xmx1600m/value /property property nameyarn.app.mapreduce.am.resource.mb/name value1024/value /property /configurationyarn-site.xml里把yarn.nodemanager.resource.memory-mb设置为合适的值比如 3G避免 Container 分配失败。这个坑极大概率会出现提前配好能省下大量排查时间。4.2 编译打包与作业提交代码写完先本地编译一遍确保没有语法错误。用 Maven 打 jar 包时必须带上依赖但千万不要带 hadoop 自身的依赖因为集群已经提供了重复打包会造成类冲突。理想的依赖排除配置是只打包你自己写的类Hadoop 依赖由集群提供。我习惯用 maven-assembly-plugin 打一个瘦包然后在提交时通过-libjars指定第三方依赖 jar。作业提交命令hadoop fs -mkdir -p /user/hadoop/movie/input hadoop fs -put /home/hadoop/ml-latest-small/ratings.csv /user/hadoop/movie/input/ hadoop jar movie-recommend-1.0.jar \ com.recommend.RecommenderDriver \ /user/hadoop/movie/input \ /user/hadoop/movie/output提交之后YARN 的 Web UI端口 8088能看到四个作业逐个运行。第一次跑通的时候看着 Progress 从 0% 升到 100%是很有成就感的。整体运行时间在伪分布式环境下大约是十几分钟ml-latest-small 数据集是完全能接受的时长。4.3 数据库初始化和结果导出Hadoop 跑完后推荐结果在 HDFS 上格式是文本行。接下来需要把这些结果导入 MySQL。推荐用两种方式。第一种是直接用hadoop fs -cat导出到本地文件再用LOAD DATA LOCAL INFILE导入 MySQLhadoop fs -cat /user/hadoop/movie/output/stage4/part-r-00000 /home/hadoop/recommend_result.txt在 MySQL 里执行LOAD DATA LOCAL INFILE /home/hadoop/recommend_result.txt INTO TABLE t_recommendation_temp FIELDS TERMINATED BY \t LINES TERMINATED BY \n;第二种是用 Sqoop 直接导入但 Sqoop 对 HDFS 文件的格式要求严格如果输出格式不是表结构反而要写自定义解析器所以我更推荐第一种——简单直接不容易出幺蛾子。导入后写一个简单的查询SELECT r.rank, r.score, m.title, m.genres FROM t_recommendation r JOIN t_movie m ON r.movie_id m.movie_id WHERE r.user_id 1 ORDER BY r.rank ASC LIMIT 10;这就完成了从 Hadoop 到业务数据库的全链路闭环。后面想加 Spring Boot 接口或前端页面展示都是在这个查询基础上扩展而已。5. 常见问题与排查技巧5.1 报错速查表在部署和运行过程中我整理了一份高频报错表每一类我都亲手踩过报错信息出现原因解决办法Container exited with a non-zero exit code 143内存不足被 YARN kill 掉调大 yarn 和 mapreduce 的内存参数FileAlreadyExistsException输出目录已存在每次运行前手动删除输出目录或在代码里判断后清理ClassNotFoundException主类名写错或依赖打包不全检查主类全限定名用hadoop jar时注意包名Invalid directory输入路径不存在确认 HDFS 路径用hadoop fs -ls检查all datanodes are badHDFS 副本异常或容量满检查磁盘空间重启 DataNodeUnsupported major.minor version 52.0JDK 版本不匹配用java -version检查版本Hadoop 3.x 需要 JDK 8Connection refused节点未正常启动检查 8088、9879 端口是否监听Partitioner not found: nullReducer 数默认是 1但需要重新分区检查 Job 的 setPartitionerClass 设置这个表放在论文的“系统测试与问题分析”章节里特别加分表明你不仅做了功能测试还做了充分的异常处理。5.2 数据倾斜问题MapReduce 里最经典的坑就是数据倾斜。在阶段二同现矩阵如果某个用户评了 1000 部电影计算两两组合时会产生约 50 万个组合这一个用户就会占据单个 Mapper 的大部分计算量造成长尾效应。我的处理方式有两层第一层是预处理阈值过滤。在数据清洗阶段设置用户评分数量阈值比如超过 200 条评分的用户进行截断处理或者将部分随机抽样丢弃。虽然会损失少量数据但能大幅降低倾斜风险。第二层是在 Mapper 内处理时增加组合数限制。比如每个用户最多只取前 100 部评分最高的电影参与组合超过的部分忽略。这样既能保留有效信息又避免单个用户的数据爆炸。这两种策略要写进论文作为“系统优化的关键手段”比单纯堆代码更能得分。5.3 伪分布式搭建的隐形坑最后分享几个伪分布式环境下的隐形坑这些在教程里很少写得细致hostname 问题必须把机器的 hostname 写入 /etc/hosts映射到本机 IP。否则集群启动后DataNode 会往真实 hostname 上注册导致 NameNode 不认。免密登录ssh-keygen 会默认生成 id_rsa.pub要把它追加到 authorized_keys。这里要注意目录权限~/.ssh 必须是 700authorized_keys 必须是 600否则 SSH 服务直接拒绝登录。格式化时机第一次配置完成后要格式化 NameNode但之后不要再随便执行hadoop namenode -format——格式化会清空元数据导致 HDFS 数据丢失。很多同学踩的坑是改完配置后直接重新格式化结果集群起不来。系统时间和集群时间如果哪一步报 Token 过期或 Lease 异常先检查系统时间是否和集群其他节点同步。伪分布式下虽然只有一台机器但时钟偏差过大也会引发问题。磁盘空间HDFS 默认副本数是 3伪分布式虽然只有一个 DataNode但副本数一定要改成 1否则磁盘很快就满了。property namedfs.replication/name value1/value /property这个配置忘改的话跑完整套推荐流程后HDFS 可能直接占掉几个 G 的磁盘空间在云主机上很容易触发磁盘告警。5.4 数据库连接和中文乱码数据库这里也有个高频问题。MovieLens 的电影标题是英文的理论上无乱码问题但如果自造数据包含中文导入 MySQL 时就容易出现编码不一致。建库时统一用 utf8mb4 字符集连接串里也显式指定CREATE DATABASE recommend_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;JDBC 连接串写成jdbc:mysql://localhost:3306/recommend_db?useUnicodetruecharacterEncodingutf8顺手提一句ratings.csv里的时间戳是 Unix 时间戳导入t_rating时记得用FROM_UNIXTIME()转换不然展示层会拿到天书一样的数字。结尾做这个项目过程中我反复改过好多次一开始想用朴素贝叶斯后来发现数据稀疏时效果不如协同过滤一开始把相似度计算全部塞进一个 Job结果 reducer 直接内存溢出一开始忘了给物品对做顺序约定推荐结果总是波动。每次踩坑都是对原理更深一层的理解。最后再分享一个经验整个项目完成后一定要写一个 README把“环境准备 → 数据准备 → Hadoop 启动 → 代码打包 → 作业提交 → 结果导出 → 数据库查询”的完整命令放进去。这不仅是为了交作业更是为了你两个月后回看自己代码还能跑起来。这个题目做到这个程度源码、数据库、论文、答辩 PPT 四件套都齐了而且每一步都有据可查。剩下的事就是你在论文里把算法公式的推导写扎实把每个 MapReduce 阶段讲清楚把测试结果贴出来这就是一个优秀的毕业设计该有的样子。本文还有配套的精品资源点击获取