☰
基于Hadoop+Spark+SpringBoot的宠物商品比价推荐系统实战
2026/10/2 9:38:39 网站建设 项目流程

宠物商品比价推荐系统这个项目,我做完之后的最大感受是:它并不是一个普通的CRUD项目,而是把传统Web开发、分布式存储、离线计算、机器学习、数据可视化全部串起来的一次全栈大数据实践。简单说,用户输入“猫粮”,系统要能同时返回哪些平台在卖、价格差距多大、哪款更值得推荐;商家看到的是价格分布和趋势;运营看到的是大屏上的实时KPI。整条链路里,Hadoop负责存原始数据,Spark负责跑比价和推荐算法,SpringBoot负责把结果包装成API,可视化大屏负责最后呈现。这篇就按真实开发顺序,把关键设计、代码片段和踩坑经验都摊开来讲,适合正在做类似课设、毕设或刚入门大数据开发的人。

1. 这个系统到底要解决什么问题

1.1 宠物商品比价的真实痛点

先聊比价场景。宠物主粮、猫砂、驱虫药的线上价格其实非常混乱,同一款“皇家猫粮”在A平台229元,在B平台可能就199元,如果赶上满减还能再低。我统计过第一批采集的3万条商品记录,同款商品跨平台价差超过15%的占了将近四成。用户想省钱,但手动对比多个平台效率太低;商家想跟价,又不知道竞品到底什么价;平台方也希望有一个价格监控体系。所以比价功能的本质,不是把一个商品的价格拉出来排序,而是要做三件事:识别同款商品、计算真实到手价、呈现价格差异和价格趋势。

识别同款是最容易翻车的。不同平台对同一品牌商品的标题写法差异很大,“渴望六种鱼猫粮5.4kg”和“ORIJEN 渴望 六种鱼 犬猫通用粮 5.4kg”其实是一个东西。如果单纯按标题完全匹配,系统会认为这是两个商品,比价就失去意义。我最后采用的方式是:先把品牌、规格、容量等结构化字段抽取出来,再结合分词和相似度算法做匹配。这一块会在数据清洗部分详细展开,因为它直接影响系统的可信度。

1.2 为什么选中Hadoop+Spark+SpringBoot这套技术栈

为什么用Hadoop+Spark+SpringBoot,而不是一台MySQL加一个定时爬虫就搞定?这里有个真实的规模判断。如果只是几百件商品,完全没有必要上Hadoop;但当我设想系统要支撑几十万商品、几十万用户行为以及每天多次全量价格更新时,单机数据库和单机脚本就撑不住了。HDFS解决海量原始文件和中间结果的存储问题,Spark解决大数据量下的计算和模型训练问题,SpringBoot解决对外服务的问题,三者各自只干自己最擅长的事。

另一个原因是为了完整展示大数据处理链路。很多初学者只会在本地跑一个Spark demo,连不上真实数据源;或者只会写SpringBoot,没接触过分布式存储。这套组合能把“数据从哪来、存到哪、怎么算、怎么用”这条路走通。作为课程设计或毕业设计,每一层都有东西可讲,面试也容易拿得出手。但我也提醒一句,如果你只是为一个小商城做比价,单表加索引可能更实际,技术选型要跟数据规模匹配。

2. 整体架构设计与数据流转

2.1 功能模块划分:别让每一层互相打架

整个系统按我最初的划分,包含五个模块:采集模块负责从宠物电商平台获取公开商品和价格数据;存储层由MySQL、HDFS、Redis组成,MySQL保存业务表和最近结果,HDFS保存历史原文和中间结果,Redis保存热点数据;计算层用Spark跑三个离线任务,分别是同款归并、价格统计和ALS推荐模型训练;服务层是SpringBoot应用,负责搜索、比价、推荐、大屏等REST API;展示层是可视化大屏和用户端页面。

模块之间尽量减少耦合。采集模块只负责把数据推到HDFS,不关心Spark怎么算;Spark只负责产出结果表,不直接处理用户请求;SpringBoot只读取MySQL和Redis,不需要自己操作HDFS。这样做的最大好处是,任何一个环节挂了都容易排查。比如大屏数据不更新,我只需要去查Spark任务是否成功,而不是从前端一路排查到数据库。

2.2 从商品数据到推荐结果,一条管道走通

完整数据管道大概是这样的:定时任务触发爬虫程序,爬虫把每一条商品记录写入日志文件,文件按天分区落到HDFS的/raw/pet/products/dt=2025-04-10/目录;接着Spark清洗任务读这个目录,去重、补全品牌品类、过滤异常价格,结果写到/ods/pet/products_clean/dt=...;然后比价任务和推荐任务基于清洗结果做计算,产出价格聚合表和用户推荐表;这些表由SpringBoot在启动时或Spark写完后的通知事件同步到MySQL/Redis。最终用户端查询时只访问MySQL和Redis,不会跟Hadoop打交道。

为什么中间要加一个“同步到MySQL”的步骤?因为Spark原生产物是Parquet或JSON,SpringBoot直接查Parquet不仅依赖重,查询延迟也不可控。即使Hive能从HDFS读,但每次接口请求都走Hive也受不了。所以离线计算和在线服务之间必须有一个结果落库的动作,这是大数据项目通用的数仓分层思路。

2.3 大屏指标怎么定:先把数字口径说清楚

可视化大屏不是把几张图表拼在一起。我在设计大屏之前,先跟使用方确认了三个问题:价格监控要看到什么?推荐系统有没有效果?数据源健康情况如何?最后定下这组指标:参与比价商品总数、平台数、同款商品最大价差、价格波动TOP10、各品类推荐点击率、采集任务成功率等。其中“推荐点击率”非常关键,它需要在线埋点把用户点击行为收集回来,再回传给推荐任务做效果评估。如果一开始没有定义口径,后面做统计时会被各种“率”的计算方式搞晕。

大屏这些指标由Spark负责聚合,然后存进dashboard_summary表,SpringBoot提供一个/api/dashboard/overview接口给大屏前端用。于是三层的责任也很清楚:Hadoop不直接给大屏喂数据,Spark算完,SpringBoot转发。这比大屏直接连HDFS要安全得多,也更容易控制权限。

3. 数据采集、存储与Hadoop环境搭建

3.1 商品数据源与采集策略

先说合规性和频率:我只抓取网站上公开可访问的商品页和列表页,不碰用户隐私,同时控制请求频率,避免给对方服务器造成压力。实际环境里理想情况是接入平台开放API,但如果没有,爬虫就作为补充。我用Java写采集模块,部署在一台Linux机器上,对每个电商平台写一个解析Handler。每个Handler输出统一结构的JSON行,比如:

{ "platform": "jd", "skuId": "192837", "title": "皇家猫粮 10kg", "brand": "皇家", "category": "猫主粮", "spec": "10kg", "price": 299.0, "discountPrice": 259.0, "sales": 1200, "commentCount": 356, "updateTime": "2025-04-10 12:00:00" }

每天凌晨1点到3点执行全量任务,白天每两小时做一次价格增量更新。宠物商品相对标品,更新频率不高,抓太频繁反而容易触发反爬。我踩过一个坑:第一次把爬虫调度间隔写成10分钟,运行没多久IP就被封了。后来改成每天一次全量加每两小时一次热点商品增量,数据新鲜度完全够用。

3.2 数据清洗与同款归一化:比价准不准看这里

爬下来的数据至少包含三类脏数据:字段错位、平台自定义规格、重复采集。我的清洗策略分四步:第一步做格式校验,价格、SKU这类关键字段不合法直接丢弃,只记录到错误日志;第二步做去重,同一平台同一SKU只保留最新更新时间;第三步做字段映射,把“品牌”“品类”“规格”都映射成统一字典,比如把“粮-猫-成年期”归一成“猫主粮”;第四步做同款识别,这一步是比价系统的核心,也是耗时最长的地方。

同款识别的做法是:先按brand + category + spec做粗匹配,然后对标题做分词,取词向量相似度大于0.85的合并成同一个standardSkuId。如果不放心,可以再加一个人工复核台,把算法判断“疑似同款但不确定”的记录列出来。这个人工环节听起来不“大数据”,但非常实用。后来模型上线后,同款识别准确率从82%提高到了94%,靠的就是这批人工标注样本。

3.3 Hadoop伪分布式搭建与HDFS落地

开发阶段不需要一上来就搭三台机器。我在一台8核16G的Ubuntu上做了Hadoop伪分布式,先把流程跑通。核心配置就三个文件:core-site.xml指定NameNode地址,hdfs-site.xml设置副本数为1并关闭权限检查,yarn-site.xml指定资源调度器。以下是我比较常用的一套配置:

<!-- core-site.xml --> <configuration> <property> <name>fs.defaultFS</name> <value>hdfs://localhost:9000</value> </property> </configuration> <!-- hdfs-site.xml --> <configuration> <property> <name>dfs.replication</name> <value>1</value> </property> <property> <name>dfs.permissions.enabled</name> <value>false</value> </property> </configuration>

格式化并启动:

hdfs namenode -format start-dfs.sh start-yarn.sh

为什么伪分布式下副本数必须设1、权限检查要关?因为只有一台机器,副本3会白白占磁盘;关闭权限是为了开发期省去一堆用户组配置。生产环境不能这么干。HDFS里建目录用hdfs dfs -mkdir -p /raw/pet/products,上传采集结果用hdfs dfs -put。

如果要做历史数据迁移或跨目录拷贝,hdfs distcp很有用,例如hdfs distcp hdfs://source:9000/raw hdfs://backup:9000/raw。distcp 会把大文件切块并行复制,速度远高于单线程put,同时要注意参数:-m控制并发map数,-update只复制变化文件,-delete删除目标端多余文件。我在把历史数据迁移到另一台机器时靠它省了不少时间。

3.4 Zookeeper整合与集群扩展

单机伪分布式跑起来后,下一步不是急着写代码,而是把Zookeeper也装上。因为后面Spark Standalone模式、HDFS HA、HBase都会用到ZK。Hadoop 3.x的高可用依赖ZK来选举Active NameNode,但即使不做HA,提前装好ZK也能保证你切换到集群时流程更顺。我一般用zkServer.sh启动单机实例,然后在hdfs-site.xml里配置HA相关参数,NameNode的Active/Standby状态就会自动协调。很多教程把ZK和Hadoop拆成两个部分,我在实战中更喜欢把它们整合起来,因为启动顺序和故障切换都一起验证了。

整合时有几个细节:ZK集群数量最好是奇数,开发环境单台也行;Hadoop连接ZK时,clientPort默认2181,一定要保证网络可通;格式化NameNode前先确保ZK中无残留节点,不然会报错。伪分布式下可以只启动一个ZK,写代码时把连接串配成localhost:2181,这样SpringBoot后续要接ZK也不会有额外障碍。如果你手头有两三台服务器,直接把Hadoop做成常规集群:一台NameNode/ResourceManager,两台DataNode/NodeManager,再把ZK三节点部署上,数据副本数改为2或3,Spark的Standalone集群Master挂在其中一台。现在是容器化时代,也可以用Docker把ZK、NameNode、DataNode编排起来,但学习和调试阶段,裸机伪分布式对理解进程关系帮助更大。

4. Spark核心算法实现

4.1 比价引擎不是取最低价那么简单

比价并不是一个SQL groupBy就能写完的。我先把清洗后的商品表加载成DataFrame,字段有standardSkuId、platform、price、discountPrice、freight、sales等。第一步计算到手价:到手价 = discountPrice(有效时) 否则 price,再加上运费;有些平台还有满减券,为了不完全失真,我用了预估优惠字段,规则配置在数据库里。接着用窗口函数按standardSkuId分区,排序求最低价和最高价,算出价差和价差率。最后只保留价差率大于5%的同款记录,进入比价结果表,因为价差太小没有展示价值。

这里的关键是标准SKU的粒度。如果粒度是按品牌+规格,可能把不同口味混在一起;如果粒度是具体到口味,同款价差又会被拆开。我对宠物粮是按“品牌+品类+规格+适用阶段”来定的,把口味作为属性展示,而不是参与匹配。举个例子:同品牌同规格猫粮,鸡肉味和鱼肉味价格有差异是正常的,不应该算作比价对象。这个设计直接决定了比价结果的可信度。

核心计算可以用 Spark SQL 或 DataFrame API 实现:

import org.apache.spark.sql.expressions.Window import org.apache.spark.sql.functions._ val df = spark.read.parquet("/ods/pet/products_clean") val priceDF = df.withColumn("realPrice", when(col("discountPrice").isNotNull && col("discountPrice") > 0, col("discountPrice")).otherwise(col("price")) + col("freight")) val windowSpec = Window.partitionBy("standardSkuId") val result = priceDF .withColumn("minPrice", min("realPrice").over(windowSpec)) .withColumn("maxPrice", max("realPrice").over(windowSpec)) .withColumn("priceDiff", max("realPrice").over(windowSpec) - min("realPrice").over(windowSpec)) .withColumn("diffRate", format_number( (max("realPrice").over(windowSpec) - min("realPrice").over(windowSpec)) / min("realPrice").over(windowSpec) * 100, 2)) .filter(col("diffRate") >= 5)

这个写法比单纯用SQL更容易做逻辑封装,加上注释后,维护起来也清楚。

4.2 推荐系统:ALS协同过滤实战

推荐部分我选的是ALS协同过滤,原因是它实现简单、效果稳定、适合离线批量训练。训练数据用的是用户浏览、收藏、购买行为折算成的评分:浏览计1分、收藏计3分、购买计5分。没有真实行为数据时,可以先用程序生成一批模拟用户行为,但后期最好接入真实埋点数据,否则算法只是demo。

训练代码大致如下:

import org.apache.spark.ml.evaluation.RegressionEvaluator import org.apache.spark.ml.recommendation.ALS val ratings = spark.read.parquet("/ods/pet/user_ratings") val Array(training, test) = ratings.randomSplit(Array(0.8, 0.2)) val als = new ALS() .setMaxIter(10) .setRank(12) .setRegParam(0.1) .setUserCol("userId") .setItemCol("productId") .setRatingCol("rating") .setColdStartStrategy("drop") val model = als.fit(training) val predictions = model.transform(test) val evaluator = new RegressionEvaluator() .setMetricName("rmse") .setLabelCol("rating") .setPredictionCol("prediction") val rmse = evaluator.evaluate(predictions)

这里有几个参数我反复调过。rank是隐因子个数,设太小模型学不到规律,设太大训练时间和内存都涨,我对当前数据量用12左右,数据量大到百万级再往上加。maxIter设为10,超过15之后RMSE基本不降。regParam是正则项,数据稀疏时调大一点防止过拟合。coldStartStrategy必须设成drop,否则测试集里从未出现过的用户或商品会得到NaN预测,评估就崩了。

模型训练完之后,调用model.recommendForAllUsers(10)给每个用户生成10条商品推荐,再把结果explode成一行行的userId, productId, rating,方便回写MySQL。对于新用户没有行为数据,就用热销榜兜底,所以推荐接口一定是“协同过滤结果 + 热销商品”两层结构。

4.3 计算结果回写与任务调度

Spark任务产出的结果要落库。最稳妥的方式是foreachPartition批量写入MySQL,每个分区建一个数据库连接,然后批量执行插入。注意批次不能太大,我用500条一批,防止MySQL锁和内存压力。目标表要加唯一索引,比如recommend_result表的唯一键是(user_id, product_id, rank),比价结果表的唯一键是(standard_sku_id, platform),这样重复跑任务时用INSERT ... ON DUPLICATE KEY UPDATE,不会产生垃圾数据。

任务调度方面,我一开始用Linux crontab,后来因为要处理依赖关系,比如比价任务必须先于推荐任务,改成在shell脚本里按顺序调用spark-submit。简单但够用。更工程化的做法是引入Azkaban或DolphinScheduler,但对于课设和演示,脚本加crontab反而更容易讲清楚。

5. SpringBoot后端服务与接口设计

5.1 工程结构与核心配置

SpringBoot部分就是标准的Maven项目,Java包按controller、service、mapper、entity、config分。application.yml里主要配置了三块:MySQL数据源、Redis连接、自定义的推荐结果缓存前缀。因为Spark已经提前把结果写到MySQL,SpringBoot不再需要连接Hadoop客户端。这个设计在依赖上省了很多麻烦,也不用在Web层引入一堆hdfs-client和spark依赖,启动速度会快很多。

server: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/pet_compare?useUnicode=true&characterEncoding=utf8 username: root password: 123456 redis: host: localhost port: 6379 mybatis-plus: mapper-locations: classpath:mapper/*.xml

如果你在IntelliJ IDEA里启动,记得检查Language Level和Maven编译器版本,我遇到过项目从JDK8切到JDK11后,某些依赖行为有差异,导致本地启动和部署不一致。建议大家在项目里固定一个Java版本,不要轻易更换。

5.2 接口设计:搜索、比价、推荐一站式

接口设计上我按场景分三类。搜索接口/api/product/search,参数有keyword、category、sort、page、size,先走MySQL关键字匹配,再配合Redis缓存热词搜索结果。比价接口/api/compare/same?standardSkuId=xxx,返回该同款商品在不同平台的价格、到手价、价格差、最后抓取时间。推荐接口/api/recommend?userId=xxx,优先读Redis里的推荐列表,如果没有再查表,最后拼上热销榜兜底。

统一返回结构建议用类似Result的类封装,减少前端解析成本。接口文档可以直接用Swagger或knife4j,大屏和用户端都能直接看。这一层主要负责把离线结果以合适的姿势暴露出来,不建议在接口里做复杂计算,否则会把服务层拖垮。举个例子,搜索排序如果需要把“价格优势率”综合加权,这个指标应该在Spark里算好存起来,SpringBoot只管取数排序。

接口作用关键返回字段
/api/product/search商品搜索商品ID、标题、价格、平台
/api/compare/same同款比价各平台价格、到手价、价差率
/api/recommend商品推荐推荐商品ID、评分、原因
/api/dashboard/overview大屏聚合数据KPI、图表数据、采集状态

5.3 缓存与性能优化

推荐结果是一次性算好的,用户量不大时其实直接查表也很快。但为了演示性能优化,我加了一层Redis。用户请求推荐时先走Redis,没有缓存再查MySQL并回填缓存,缓存过期时间设为2小时。这里有个细节:Redis key必须带上算法版本号,比如rec:user:1024:v2,否则更新算法后老用户还会拿到旧结果。

搜索接口的热词结果也可以缓存,比如前10个宠物热搜词对应的商品ID列表缓存30分钟。SpringCache用起来很简单,在service方法上加@Cacheable注解就能做到,但你要自己设计缓存失效策略,避免数据更新后缓存一直不刷新。每次Spark任务跑完,我会主动调用一个清理缓存接口,把推荐和比价相关的缓存全部删除,这样大屏和用户端在下一次请求时自然拿到新数据。

6. 可视化大屏开发实战

6.1 大屏数字与图表选型

大屏首先要解决“放什么数字”。我给宠物商品比价推荐系统的大屏设计了六个区域:顶部是核心KPI,包括商品总数、平台数、今日采集量、平均价差率;中间左侧是“各平台价格区间分布”横向条形图;中间右侧是“同款商品价差TOP10”排行榜;中间主视觉放“全网宠物商品价格走势”折线图;底部左侧是“推荐商品点击率”柱状图;底部右侧是“品类销量占比”饼图或南丁格尔玫瑰图。

图表选型的逻辑很简单:横向条形图适合平台名较长的场景,折线图强调趋势,排行榜适合比价场景,饼图适合看占比。不要为了炫技选3D地球、飞线这种花哨图表,除非数据真的跟地理有关。大屏看的是信息密度和可读性,不是视觉效果。大屏很多时候是给答辩老师、运营或领导看的,所以指标必须直白,图例要跟业务语言一致。

6.2 从MySQL到ECharts的联动

前端我用Vue加ECharts,数据从SpringBoot的/api/dashboard/overview统一获取。这个接口返回一个JSON对象,里面包含所有大屏需要的指标和图表数组。前端拿到数据后,按图表ID更新对应option.data,再调用setOption。代码结构大概是这样:

fetch('/api/dashboard/overview') .then(res => res.json()) .then(data => { priceChart.setOption({ xAxis: { data: data.priceRange.categories }, series: [{ data: data.priceRange.values }] }); rankList.innerHTML = data.diffTop10.map(item => `<li>${item.productName} 价差${item.diffRate}%</li>` ).join(''); });

接口返回的数据结构最好在Spark和SpringBoot之间就定好。我在项目里遇到过Spark算出的字段名是snake_case,前端却用camelCase,结果图表显示为空。最后约定:大屏接口统一返回camelCase,字段说明维护在文档里。相当于给大屏建了一个轻量级数据契约。

6.3 大屏布局与刷新策略

大屏设计稿我按1920x1080来做,前端用scale方式整体缩放,这样在16:9的屏幕上不会出现滚动条。如果现场屏幕是竖屏或异形屏,需要单独调布局,不能只改两行CSS就完事。布局用CSS Grid,分成上中下三层,左右宽度大概280px和340px,中间留给价格走势图。

刷新策略用30秒轮询。不要用WebSocket做实时推送,除非你有流式计算。Spark任务本来就是离线批次,30秒一次完全够。另外大屏运行时间长了会有内存泄漏问题,尤其是ECharts实例重复创建。我用的是每个图表只初始化一次,每次更新只setOption;整页刷新可以采用定时location.reload,每2个小时强制刷一次,兜底释放内存。

还有一个细节:大屏如果长时间挂着,采集任务正好在凌晨跑,MySQL连接池和前端页面注意不要跨天出问题。我在部署时给大屏页面加了夜间自动刷新,保证第二天打开就是最新数据。

7. 环境兼容、踩坑记录与性能调优

7.1 版本匹配建议

大数据技术栈最怕版本不兼容。我最后稳定运行的组合是:Hadoop 3.3.4 + Spark 3.3.1 + Zookeeper 3.7.1 + SpringBoot 2.7.x + JDK 8/11 + MySQL 8.0.x + Redis 6.x。这个组合经过实测,Spark官方预编译版本对应Hadoop3,不需要自己重新编译。SpringBoot 2.7用javax.*命名空间,如果你的代码想用jakarta.*就必须升到SpringBoot 3.x,但同时要JDK17,会牵一发动全身,所以课设项目别盲目追求新版本。

另一个常见坑是IDEA里的Maven依赖冲突。SpringBoot项目引入spark-sql或者hadoop-client时,会把一大堆传递依赖带进来,跟SpringBoot自带的Logback、Jackson冲突。我的做法是,把Spark任务单独放一个Maven模块,不跟SpringBoot放同一个依赖树;如果必须放一起,用exclusions把Spark依赖里的log4j、jackson-module-scala排除掉。

7.2 常见问题排查速查表

以下是我在实际调试中遇到频率最高的问题,整理成了速查表,按“现象-原因-处理”三列写,方便你现场对着排查。

现象常见原因处理办法
Hadoop NameNode启动失败格式化未完成或临时目录残留删除临时目录重新执行hdfs namenode -format
Spark任务执行OOMdriver或executor内存不足,或collect()拉了太多数据调大内存参数,把collect改成save或foreachPartition
Spark读HDFS权限异常伪分布式下权限未关闭开发期设置dfs.permissions.enabled=false
SpringBoot启动NoClassDefFoundErrorSpark/Hadoop依赖与SpringBoot冲突独立Maven模块或排除传递依赖
大屏接口偶发超时MySQL慢查询或Spark正在重算检查联合索引、错峰运行Spark任务
推荐列表为空训练集交互太少或coldStartStrategy未设增加用户行为数据,设drop策略

7.3 Spark与HDFS调优笔记

调优这块我不讲复杂公式,只讲几个亲测好用的设置。HDFS副本数在伪分布式下一定要改1,否则磁盘占用翻倍;集群环境下副本数保持3,数据可靠性优先。Spark默认并行度跟文件块数走,如果小文件特别多,需要先合并文件,用repartition(200)或者coalesce控制输出文件数量。比价任务输出结果只有几万行,千万别硬编码成200个分区,否则会产生大量小文件,回写MySQL时连接占用非常难看。

Spark内存配置要结合本机内存。我的开发机16G,跑伪分布式集群时,给Hadoop NameNode和DataNode各留2G,Spark driver和executor各给2G,其余留给操作系统和IDE。executor内存给很大不一定快,反而GC时间变长;数据量小的时候1G-2G最稳。另外如果使用Python写Spark,注意pythonWorker内存是独立进程,必要时调整PYSPARK_PYTHON和PYSPARK_DRIVER_PYTHON配置。

最后一点是监控:Spark Web UI的4040端口救过我很多次。任务卡住时,我习惯先看Stage和Executor面板,确认是不是某个任务在反复重试。如果看到Data Shuffle量异常,多半是数据倾斜,需要加盐或调整key的粒度。这些老经验在课设和大屏演示前尤其有用,提前用UI检查任务状态,能避免现场翻车。

这套系统做到最后,我自己最大的体会是:真正花时间的不是写代码,而是让数据干净、让环境稳定。如果你也在做一个类似的基于Hadoop+Spark+SpringBoot的推荐或比价项目,我的建议是先把数据管道跑通,再调算法,最后才做界面;不然大屏画得再漂亮,后端拿不出可验证的数据,一切都是白费。最后分享一个小细节:在Spark写MySQL时,连接串里加上useServerPrepStmts=true和cachePrepStmts=true,批量写入速度会明显提升,这是我调了一晚上才发现的。希望这篇实践记录对你有用。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询