ARTICLE DETAIL

资讯详情

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

基于SSM与Spark的电影推荐系统:从零实现协同过滤离线推荐

基于SSM与Spark的电影推荐系统:从零实现协同过滤离线推荐 简介面向计算机毕业设计的大数据推荐系统完整项目基于SSM与Spark框架实现电影推荐功能涵盖项目文档与全部源码适合需要完成相似课题或入门大数据开发的读者。资源共1419个文件、约90.98MB主要包含Java与Scala源码、前端HTML/CSS/JS页面、图片素材、配置文件与SQL脚本等目录结构完整并带有部署说明和数据库脚本便于快速运行与二次开发。项目覆盖数据采集、清洗、统计、建模与实时计算等环节涉及Spark批处理与流处理、SSM分层架构、基于内容与协同过滤的推荐算法等核心要点从环境配置到算法实现均有相应文件支撑可帮助读者理解个性化推荐的完整实现链路。已有25人学习下载作为毕业设计参考或大数据实战演练具有较高的借鉴价值能够为同类选题提供可落地的完整方案。1. 基于SSM与Spark的电影推荐系统一套能从零跑通的大数据毕设方案如果你正在做大数据的课程设计或毕业设计选题看到“基于ssmspark的电影推荐系统”这类标题第一反应通常是两个极端要么觉得难度太大Hadoop、Spark、Java后端一整套不知道怎么串起来要么觉得就是个“网页 一个推荐列表”的演示项目没什么技术含量。这两个判断都有偏差。真正落到代码和文档上它是一条完整的离线推荐链路SSM负责后台和接口展示Hadoop负责存放批次数据Spark负责把用户的评分记录通过协同过滤算成“可能喜欢”的TopN电影列表最后回写数据库供前端查询。它能帮你在一个项目里同时讲清楚Java开发、大数据存储和推荐算法三件事也是面试时最能直接聊的技术点。适合的人群很明确准备毕设答辩的在校生、想往大数据方向转的Java开发以及需要一个可复现样例来学Spark MLlib的入门者。2. 架构选型与数据流为什么是SSM、Hadoop和Spark绑在一起2.1 整体链路一次推荐结果是怎么算出来的先看整体。这套系统对外是一个电影网站用户能注册、打分也能看到“猜你喜欢”的列表。但这些推荐结果不是Web应用里实时算的而是来自离线计算任务。常见做法是MySQL存用户、电影和评分记录HDFS存历史日志或从MySQL导入的批数据Spark定期跑一次训练任务把每个用户的TopN推荐写回MySQL的推荐表SSM后端再按userId查这张表返回给页面。整个过程看起来像一套流水线每一层只有一个明确职责。Hadoop在这个项目里真正承担的是HDFS和YARN。本地练习时HDFS用来放从数据库导出的评分快照YARN负责给Spark任务分配资源。即便你的笔记本只有8G内存也建议把Hadoop和Spark装成伪分布式而不是直接跑Spark本地模式——因为答辩时老师一定会问“Hadoop集群里每个角色的作用”没跑过伪分布式这个问题很容易答空。2.2 选型理由SSM管“给人看”Spark管“算出来”为什么是SSM而不是Spring Boot这不是技术先进性的问题而是覆盖面问题。在很多高校的课程体系里SSM仍然是Java Web的标准配置用SSM能让答辩老师觉得你基本功扎实而且项目文档里能写“Spring管理业务对象SpringMVC对外提供接口MyBatis操作数据库”每一层都有话可说。Spark选型则是因为它比原生MapReduce好写太多。协同过滤的ALS算法在Spark MLlib里直接封装好了训练、预测、评估都是几行代码的事。如果换成MapReduce去实现矩阵分解你得手写迭代计算和矩阵更新代码量和出错概率完全不是一个量级。再加上Spark天然支持Scala和Java和项目里的其他Java代码能共用依赖所以用Spark做推荐计算是这个场景下最顺的方案。2.3 数据模型与算法ALS协同过滤为什么是默认主力电影推荐场景下最常用的算法是交替最小二乘ALS。它属于协同过滤里的隐语义模型核心思路是把“用户-电影评分矩阵”分解成两个低维矩阵一个代表用户的隐特征一个代表电影的隐特征再用这两个矩阵相乘去预测缺失的评分。ALS在Spark里的落地非常标准输入三列分别是userId、movieId、rating输出是模型文件或直接的推荐结果。你要关心的参数只有几个矩阵分解的rank维度一般取10到50迭代次数maxIter10次左右就够收敛正则化参数regParam默认0.1左右防止过拟合。这个算法选型能保证你在文档里写“基于用户行为做个性化推荐”同时实现成本又足够低不会在毕设阶段把自己卡死。3. 动手落地从环境准备到Spark作业跑通的完整流程3.1 环境与版本映射先定版本再动手做这个项目最怕的不是代码写错而是组件版本互相不兼容。建议第一次搭建时直接抄下面这张版本表不要追求最新版因为网上搜到的教程和报错基本都是按某个成熟版本组合来的。组件版本推荐关键说明JDK1.8不要用17很多老教程和插件还在依赖Java 8语法Hadoop2.7.x 或 3.1.x配Spark请认准编译时对应的hadoop版本Spark2.4.x 或 3.1.x下载pre-built版本避免自己编译MySQL5.78.0也兼容注意JDBC驱动换8.x版本Maven3.6用来管理SSM后端依赖我的建议是Spark选2.4.x配Hadoop 2.7.x这套组合最成熟、网上的坑都有现成答案。环境变量里配好JAVA_HOME、HADOOP_HOME、SPARK_HOME然后把Hadoop的bin目录和Spark的bin目录都加进PATH。验证方式很简单分别执行hadoop version和spark-shell --version能正常输出版本说明环境变量没问题。3.2 准备数据建表与导入评分记录建模阶段不要一上来就追求百万级数据先用本地MySQL建好三张核心表用户表、电影表、评分表。推荐表的字段也一并建好方便Spark算完往里写。CREATE DATABASE IF NOT EXISTS movie_recommend DEFAULT CHARACTER SET utf8mb4; CREATE TABLE movie_recommend.movies ( movie_id INT PRIMARY KEY, title VARCHAR(200), genres VARCHAR(100) ); CREATE TABLE movie_recommend.ratings ( user_id INT, movie_id INT, rating FLOAT, ts BIGINT, PRIMARY KEY (user_id, movie_id) ); CREATE TABLE movie_recommend.rec_result ( user_id INT, movie_id INT, score FLOAT, rank INT, PRIMARY KEY (user_id, movie_id) );建表逻辑说明rating表的主键设计成联合主键是为了防止同一用户对同一电影重复评分rec_result表里专门留了rank字段前端查询时可以直接按这个字段排序展示“猜你喜欢”。数据集方面如果导数据不方便直接写几行Java代码或SQL脚本模拟200个用户、50部电影、几千条评分也能跑通整个流程。真实感更强的做法是去下载MovieLens数据集把里面的movies.csv和ratings.csv改造后导入MySQL。3.3 Spark作业读数据、训练模型、生成推荐结果环境准备好了数据表也建好了接下来是核心环节写Spark作业。这里我习惯用Scala写训练逻辑因为MLlib在Scala下的API最顺。把下面的代码打包成jar或者直接在spark-shell里逐行跑。import org.apache.spark.sql.SparkSession import org.apache.spark.ml.recommendation.ALS val spark SparkSession.builder() .appName(MovieRecommendALS) .master(yarn) .config(spark.sql.shuffle.partitions, 4) .getOrCreate() // 读取MySQL评分表注意连接串里的编码参数 val ratings spark.read .format(jdbc) .option(url, jdbc:mysql://localhost:3306/movie_recommend?useUnicodetruecharacterEncodingutf8) .option(dbtable, ratings) .option(user, root) .option(password, 123456) .load() .selectExpr(cast(user_id as int) userId, cast(movie_id as int) movieId, cast(rating as float) rating) ratings.cache() val als new ALS() .setRank(12) .setMaxIter(10) .setRegParam(0.1) .setUserCol(userId) .setItemCol(movieId) .setRatingCol(rating) val model als.fit(ratings) // 为每个用户生成Top20推荐 val recommendations model.recommendForAllUsers(20) recommendations.show(false)这段代码的逻辑很直白用JDBC加载MySQL里的评分数据转成Spark需要的三列格式然后交给ALS训练。setRank(12)表示把用户和电影各自映射成12维隐特征这个值不是越大越好数据量小的时候取8到15更合适setRegParam(0.1)是正则化系数它能让模型不过度依赖某些极端评分recommendForAllUsers(20)会给每个用户返回20部电影包含预测评分和排名。训练完成后需要用一段额外的代码把结果写回MySQL。最省事的写法是转成DataFrame后调用df.write.jdbc指定目标表名rec_result和连接信息即可。注意写回时把预测评分字段改名为scorerank字段用row_number().over(Window.partitionBy(userId).orderBy(desc(rating)))生成这样SSM查出来直接就是有序的推荐列表。3.4 SSM后端接入写推荐接口并返回给页面Spark算完的数据已经落在MySQL的rec_result表里后端要做的事就很单纯了按当前登录用户的userId查这张表把结果拼成JSON返回给前端页面。RestController RequestMapping(/recommend) public class RecommendController { Autowired private RecommendService recommendService; GetMapping(/top/{userId}) public Result getTopRecommend(PathVariable Integer userId) { ListRecommendMovieVO list recommendService.getTopNByUser(userId); return Result.success(list); } }对应的MyBatis Mapper里就是一条SQLSELECT m.title, m.genres, r.score FROM rec_result r LEFT JOIN movies m ON r.movie_id m.movie_id WHERE r.user_id #{userId} ORDER BY r.rank ASC。这样Controller只做接口转发Mapper只做查询推荐结果从哪来、怎么算的全被隔离在后端看不见的地方。答辩时你可以先展示页面效果再带着老师去看Spark的日志和模型输出项目完整性一下就立住了。4. 避坑指南Hadoop和Spark联调最容易踩的四个问题4.1 NoSuchMethodErrorHadoop与Spark版本不匹配现象Spark作业提交后很快报错java.lang.NoSuchMethodError: org.apache.hadoop...整个堆栈指向HDFS或YARN的API。原因Spark编译时依赖的Hadoop版本和你本机安装的Hadoop版本不一致。最常见的是Spark 3.2以上用了Hadoop 3.x的API但你本机装的是Hadoop 2.7或者反过来。解决把Spark换成本机Hadoop对应编译版本。最省事的办法是下载Spark安装包时直接选带hadoop2.7或hadoop3.1字样的预编译版本然后重新启动spark-shell再跑一遍数据读取。这个和代码本身无关纯粹是版本对齐问题不要浪费时间改代码。4.2 ExecutorLostFailureSpark作业一提交就失败现象作业跑几秒后YARN界面里看到Executor反复挂掉日志提示Container killed by YARN for exceeding memory limits。原因Spark默认每个Executor内存只有1G但你的评分数据量偏大或者用户数较多导致shuffle时数据膨胀超出了容器限制。解决提交任务时显式指定资源参数例如spark-submit --executor-memory 2g --driver-memory 1g --conf spark.shuffle.memoryFraction0.4。本地练习时重点调executor-memory和spark.sql.shuffle.partitions把shuffle分区数调小一点比如4到8个可以减少大量小文件带来的内存开销。4.3 推荐表里中文乱码MySQL连接串缺了编码参数现象Spark回写MySQL后SSM在页面上查出来的电影标题全是???或乱码。原因JDBC连接串没有指定字符集。Spark的JDBC读写默认使用平台字符集在英文环境或默认环境下中文就会丢。解决所有涉及MySQL的jdbc URL都加上useUnicodetruecharacterEncodingutf8SSM里的数据库连接串同样加上。这个坑不起眼但等页面展示时才发现你还要重新训练一遍Spark很耽误时间。4.4 数据倾斜某部热门电影让作业卡死在某个Stage现象作业日志显示某个Stage运行极慢其他Executor都已经Finished只有一个Executor卡在处理几百MB数据的task。原因MovieLens这类数据里少数电影被大量用户评分这会导致ALS在shuffle阶段某个key的数据量远超其他key单个task负载失衡。解决最简单的方法是训练前先做一次过滤把评分总数超过阈值比如平均评分次数的3倍的影评降采样或直接去掉或者在ALS训练时调高regParam减小异常权重。如果是作业规模大可以考虑用repartition重新打散数据后再训练。5. 进阶技巧给推荐结果加一个Redis缓存层当离线推荐链路跑通以后你会发现每次用户打开首页SSM都要去MySQL查一次rec_result表。虽然Rec_result表不大但在演示答辩时如果多个人同时操作MySQL的并发查询压力会直接反映成页面卡顿。这时的进阶做法是引入Redis缓存。具体设计不复杂。Spark作业完成推荐结果回写MySQL后再由一段Java代码把rec_result里的记录同步进Rediskey设计成user:recommend:{userId}value用JSON数组存Top10电影ID和标题TTL设成1小时。SSM查询推荐接口时先查Redis查得到就直接返回查不到再回调MySQL并把结果回填Redis。这套逻辑在Spring里只需要加一个Cacheable注解或几十行代码但答辩时讲出来技术层次会明显比单纯“SSM查MySQL”高出一截。Service public class RecommendService { Autowired private StringRedisTemplate redisTemplate; Autowired private RecommendMapper recommendMapper; // 先查Redis没有则查MySQL并回填 public ListRecommendMovieVO getTopNByUser(Integer userId) { String key user:recommend: userId; String cacheValue redisTemplate.opsForValue().get(key); if (cacheValue ! null) { return JSON.parseArray(cacheValue, RecommendMovieVO.class); } ListRecommendMovieVO list recommendMapper.selectTopNByUser(userId); redisTemplate.opsForValue().set(key, JSON.toJSONString(list), 1, TimeUnit.HOURS); return list; } }这段代码的逻辑就是典型的缓存旁路模式。需要注意的坑是序列化用StringRedisTemplate时value必须是JSON字符串否则会写入一堆二进制乱码TTL不要设置太长因为Spark的推荐结果通常每天刷新一次缓存过久反而会让用户看到老旧推荐。另外Redis缓存命中率可以简单看日志观察如果每次请求都穿过Redis打到MySQL就检查一下key拼接是不是和回填时一致。完成了这层改造你的项目就不只是“跑通”了而是一套有缓存意识、有离线调度意识的小型推荐系统。答辩时从数据导入、Spark训练、结果回写、Redis缓存一条链路讲下来任何一个环节被追问你都能拿出实际配置和日志应对。这是我做类似项目时最深刻的体会——把边角问题提前解决比追求模型精度重要得多。希望帮到你。本文还有配套的精品资源点击获取
返回列表