
如果你跟我一样接过“做一个商品推荐系统”这种听起来很唬人、实际更唬人的活儿大概率会有这段经历先是兴奋地画架构图觉得SpringBoot加上SpringCloud就是微服务了前端套个Vue就能可视化爬虫抓点数据就完事。等你真正把项目落地才会发现这条链路里藏着太多坑——数据从哪来、怎么清洗、推荐算法怎么算、微服务怎么拆、可视化怎么接每一环都可能让人折腾到半夜。这篇文章是我做完这套微服务分布式商品推荐系统之后的完整复盘。技术栈就是标题那套SpringBoot、SpringCloud、Vue配合分布式爬虫和大数据组件实现商品数据的采集、处理、推荐和可视化展示。不管你是准备做毕业设计、想在公司搭建推荐系统还是单纯想了解一套全栈微服务项目如何从零落地这篇文章都会给你一套可复现的路径以及我在实际项目里踩过、也填平了的坑。1. 系统全貌一条从商品数据到推荐结果的生产链路1.1 业务场景与技术栈选型先说说这个系统到底做什么。核心业务是企业需要一个商品推荐平台用户进入系统后能看到个性化推荐的商品列表、热门榜单、以及商品详情页的关联推荐。推荐的依据是商品本身的属性标题、分类、价格和用户的行为数据浏览、加购、下单。要支撑这个业务需要回答四个问题商品数据哪里来、用户行为怎么记录、推荐结果怎么算、前端怎么展示。先看技术栈选型这是我做架构判断时比较纠结的一环。后端选了SpringBoot SpringCloud Alibaba理由是SpringBoot的开发效率高SpringCloud的生态足够完整Nacos既能做注册中心又能做配置中心省去额外部署Eureka和Config Server的成本。前端用Vue我用的Vue 3 Element Plus考虑到项目需要展示推荐效果、数据统计图表Vue配合ECharts非常顺手。数据采集用Python爬虫因为Python在爬虫生态里的优势实在太明显requests、lxml、Scrapy都是现成轮子。大数据部分引入了Spark和FlinkSpark用于离线批量计算推荐结果Flink处理实时行为数据的流式计算。消息中间件用Kafka把爬虫采集的数据和实时行为数据都接入这里做解耦。技术选型不是追新而是看团队能不能维护、社区资料多不多、问题好不好排查。我用这套组合是因为每一个组件都有非常成熟的社区方案遇到问题基本都能搜索到解决方案这在项目交付阶段是巨大的隐形成本节省。1.2 为什么必须上微服务以及哪些模块真的需要独立部署很多人一说微服务就激动把所有业务都拆成一个小得不能再小的服务结果一个简单项目拆了十几个模块开发期痛苦、部署期更痛苦。我的判断标准很简单只有当服务之间有独立的扩缩容需求、独立的资源消耗特征、或者需要独立复用和团队分工时才值得拆分。在这个项目里我最终拆了六个核心服务。商品服务负责商品信息的CRUD和缓存用户服务管理用户注册登录和画像标签行为服务接收用户点击、加购、下单等行为数据写入Kafka推荐服务是核心消费Kafka和读取离线推荐结果对外提供推荐接口搜索服务基于Elasticsearch做商品搜索网关服务统一路由和鉴权。爬虫不是微服务而是一个独立运行的Python任务系统但它通过Feign接口把采集结果回传到商品服务所以整个系统对外仍然是一个整体。拆完之后要解决分布式带来的问题服务发现、配置管理、负载均衡、熔断限流这些交给Nacos、OpenFeign、Sentinel去处理。每一个细节都很有意思后面我会拆开讲。现在先让整体架构在脑子里有个轮廓爬虫采数据进KafkaSpark和Flink消费数据计算推荐结果推荐服务读取结果对外提供接口Vue前端通过Gateway网关调接口把推荐效果可视化呈现出来。1.3 项目整体架构与数据流转整个系统的数据流转链路是这样的爬虫从目标电商平台采集商品信息包括标题、价格、销量、店铺、评论数写入Kafka的goods-topic同时用户在前端产生的行为数据通过行为服务也写入Kafka的behavior-topic。Spark离线任务定时从Kafka批量拉取数据训练ALS协同过滤模型生成用户推荐列表落到Redis和MySQLFlink实时任务消费behavior-topic统计商品热度排名和用户实时偏好更新到Redis。推荐服务启动时加载离线模型结果运行时综合分析离线推荐、实时偏好和热门榜组装成最终的推荐返回给前端。前端拿到数据后在商品列表页、推荐大屏和用户个人中心三个场景做可视化展示。这个架构最核心的设计思想是“离线计算和在线计算分离”。离线计算追求准确和全面代价是延迟高适合生成候选集在线计算追求实时和轻量代价是模型简单适合做排序和兜底。两者结合才是一个生产可用的推荐系统。下面我从数据源头开始把每个环节展开讲。2. 分布式爬虫商品数据的源头工程2.1 从单机到分布式爬虫模块的整体设计商品数据是推荐系统最基础的食物没有数据一切都是空谈。我一开始写了一个单机版爬虫用requests请求商品列表页lxml配合XPath解析HTML提取商品标题、价格、销量、店铺名、商品详情链接。跑起来倒是很快但一个问题暴露无疑单机爬虫在数据量上来之后采集速度完全跟不上而且单点故障意味着整个数据管道中断。于是升级成分布式爬虫方案。这里我用了Scrapy Scrapy-Redis原理很朴实多台机器上的多个爬虫进程共享一个Redis任务队列调度器从队列里取URL进行抓取抓取到的页面继续解析出新的商品URL放回队列。这样就实现了任务的分发和去重Redis的Set数据结构天然支持URL去重不用自己再写一套BloomFilter虽然数据量极大时还是需要上BloomFilter这个项目里Set够用。最关键的一个设计是请求频率控制。电商平台对爬虫的容忍度是很低的同一个IP高频访问很快会被限制甚至封禁。我的做法是随机User-Agent池伪装不同浏览器和设备每个请求之间随机延时1到3秒不让请求间隔呈现出规律的轨迹维护一个代理IP池每个代理IP设定每日请求上限达到上限自动切换。这套组合拳不能说百分百不会触发风控但在采集阶段已经能把封禁概率降到很低。实际操作中我在商品列表页、详情页分别配置了不同的请求间隔详情页访问频率比列表页更低因为详情页的反爬策略通常更严格。2.2 爬虫数据清洗与字段标准化爬虫拿到的原始数据很难直接用。举个例子商品标题里经常夹杂各种营销词——“正品包邮”“限时秒杀”“新品上架”这些对推荐算法来说是噪声。价格字段可能是“¥99.9”这样带货币符号的字符串销量可能是“12万”“3000”这种带单位的模糊值。我的清洗规则分三层文本层去掉标题首尾的营销词、特殊符号统一全角半角分类字段映射到统一分类体系比如“手机配件”这一类目下可能有几十种写法全部做归一化映射。数值层价格字符串去掉货币符号和单位转成Decimal类型入库销量字段解析“万”这个单位12万转成120000方便后续数值运算。格式层统一商品ID、店铺ID的字符串格式日期字段统一成时间戳避免不同来源的数据格式不一致导致后续关联失败。特别提醒一点商品ID的统一是整个链路的关键。爬虫从不同页面拿到的同一个商品ID可能不一致有的详情链接里带参数有的不带我专门写了一个ID归一化模块提取详情URL中的纯数字ID部分再拼接源站标识形成全局唯一的商品ID。这个ID会贯穿推荐计算、行为关联、结果展示全链路如果这里不一致后面Spark和Flink算出来的推荐结果会直接错乱。数据清洗完成后爬虫通过Kafka Producer将标准化数据发送到goods-topic。之所以不直接写入MySQL是为了削峰填谷——爬虫的采集速度是不稳定的高峰期和低峰期差异很大如果直接写库数据库压力会忽高忽低而且Spark消费时需要批量读取Kafka的日志存储特性正好满足这个场景。3. 微服务拆分与SpringCloud组件落地细节3.1 Nacos做注册中心和配置中心版本选择与关键配置回到后端部分。SpringCloud Alibaba现在是我做微服务项目的首选因为Nacos一个组件同时干掉了注册中心和配置中心两件事。版本选择这里我踩过一个坑也是很多新人会反复踩的SpringBoot、SpringCloud、SpringCloud Alibaba三者之间有严格的版本对应关系不能随便乱配。我用的版本组合是SpringBoot 2.7.x、SpringCloud 2021.0.x、SpringCloud Alibaba 2021.0.5.0这套组合经过大量项目验证兼容性比较稳。如果盲目拉最新版本很可能遇到类找不到或者Bean加载异常这种莫名其妙的报错。Nacos的配置管理能力值得认真用起来。我把所有微服务的公共配置包括MySQL连接串、Redis地址、Kafka地址、超时时间都放到Nacos的配置中心统一管理。这样做的第一个好处是环境切换开发环境、测试环境、生产环境只需要在Nacos上维护不同namespace的配置服务启动时指定namespace即可第二个好处是配置修改后可以动态生效比如调整Feign调用的超时时间不用重启服务就能通过Nacos的监听机制推送更新。3.2 OpenFeign与Gateway网关服务间通信的完整链路微服务之间的调用我用的是OpenFeign。相比直接写RestTemplateFeign的优势是声明式接口代码看起来非常干净而且服务发现、负载均衡都是内置的。我定义推荐服务的一个Feign接口大致长这样FeignClient(name goods-service, fallback GoodsClientFallback.class) public interface GoodsClient { GetMapping(/goods/{goodsId}) GoodsVO getGoodsDetail(PathVariable(goodsId) Long goodsId); GetMapping(/goods/batch) ListGoodsVO batchGetGoods(RequestParam(ids) ListLong ids); }这里有两个细节必须说。第一fallback熔断降级一定要配。商品服务万一挂了推荐服务不能跟着挂降级逻辑返回一个默认的商品信息或者直接滤掉推荐列表里的这些商品比抛异常让用户看到错误页强一百倍。第二Feign的超时时间要和网关的全局超时时间做一致性匹配。如果网关超时设了3秒但Feign调用内部服务超时设了10秒前端3秒就断开了内部服务还在傻等白白浪费连接资源。网关这块我选的是SpringCloud Gateway基于WebFlux性能和响应式编程模型都符合微服务入口的要求。我在Gateway里做了统一的鉴权前端登录后拿到JWT每次请求在网关层校验Token合法性校验通过再路由到目标服务如果Token过期或者非法直接返回401根本不需要请求进到业务服务里这比在每个服务里写一套过滤器省太多事了。3.3 Sentinel限流熔断保护推荐服务不被流量打垮推荐服务面对的是所有用户的请求入口特别容易被突发流量打垮比如大促、秒杀、集中访问。我引入Sentinel做限流和熔断。限流规则按QPS维度配置每个用户ID每秒最多允许10次推荐请求超过就排队实在排不下就快速失败。熔断规则按接口的错误比例配置3秒内错误率超过30%熔断器打开后续请求直接走降级逻辑等30秒后试探恢复。这一套规则让我在压测时非常安心即使后端推荐引擎压力爆表网关和Sentinel会把流量挡在门外保证整个系统不雪崩。还有一个容易忽略的点Sentinel的规则可以配置在Nacos上动态推送实现规则的热更新。我最初把规则写在代码里改一个阈值就要重新发版后来全部迁移到Nacos配置通过Sentinel控制台实时调整效率提升非常明显。4. 推荐引擎从协同过滤到大数据组件的实战整合4.1 推荐算法选型ALS协同过滤与商品相似度计算接下来是整个系统最核心的部分——推荐引擎。算法选型上我用了两种组合离线用Spark MLlib提供的ALS交替最小二乘协同过滤算法在线用商品相似度矩阵做关联推荐。先讲ALS。如何拿到“用户–商品–评分”的数据是第一个问题因为我们的业务里没有显式的评分数据只有行为数据。我的转换规则很简单用户浏览商品记1分加购记3分下单记5分。这就构成了ALS的标准输入——Rating对象的三元组。ALS算法会把这些行为数据分解成用户特征矩阵和商品特征矩阵两个矩阵在隐藏特征空间里做内积就能预测用户对未交互商品的偏好评分把评分最高的N个商品作为推荐候选集。关键在于Spark任务怎么跑。我的离线任务设计成每天凌晨执行一次从Kafka批量消费前一天的全量行为数据整合商品特征训练ALS模型输出每个用户Top50的候选商品列表写入Redis缓存和MySQL。模型训练时有两组参数要重点调——Rank隐藏特征数和正则化参数。Rank设太小特征表达不够推荐效果差设太大训练时间成倍增加且容易过拟合。我在测试环境跑了一组对比Rank为50、正则化参数为0.01时在AUC指标上表现最优这个参数组合固化到了代码配置里。4.2 在线推荐实时偏好与热门榜的兜底策略只有离线推荐是不够的。用户刚看了某件商品希望立刻就能刷到相似推荐离线任务要等一天根本来不及。所以我加了在线推荐链路Flink实时消费行为数据计算两个维度的实时结果——用户当前会话最喜欢的商品分类标签以及全站实时商品热度榜。这两个结果都更新到Rediskey分别设计成user:realtime:{userId}:category和goods:hot:ranking。推荐服务最终返回的结果我按这个优先级组合实时偏好推荐的相似商品占大头离线协同过滤结果补充中长尾热门榜兜底保证冷启动用户也有内容可看。这个组合的本质是既尊重用户当下意图又不忘历史偏好还能保证新用户不至于面对空列表。系统上线后推荐点击率首版就达到了15%这个混合策略功不可没。4.3 hanlp分词在商品推荐中的应用偷偷分享一个让推荐效果明显提升的小改动用hanlp分词处理商品标题构建商品的特征标签。商品标题里其实藏着大量结构化信息比如“Apple iPhone 15 Pro Max 256GB 原色钛金属 5G手机”通过分词和词性标注能提取出品牌、型号、容量、颜色、品类等关键标签。把标签用于两个场景一是基于内容特征的相似商品召回商品标题包含相同核心词就提高相似度权重二是构建用户偏好画像用户历史浏览商品的核心标签聚合在一起就是这个人近期关注的方向。hanlp在SpringBoot里的集成很简单引入hanlp的jar包后调用分段器和词性标注器一行代码就能拿到分词结果。算是一个投入产出比很高的功能点。5. Vue前端与可视化大屏把推荐结果“画”出来5.1 前端工程化与动态路由设计前端这部分我用了Vue 3 Vite Element Plus的组合。Vite比Webpack的启动速度快太多开发体验是质的提升。路由设计上没有用写死的静态路由表而是根据用户角色动态生成——登录后前端拿到用户的权限标识通过addRoute动态挂载路由。推荐系统里普通用户和管理员的可见页面不同管理员能看到数据大屏和爬虫监控普通用户只看商品推荐和个人中心这套动态路由方案很好地满足了多角色场景。前后端分离的联调是另一个重点。开发阶段我用Vite的代理把/api路径转发到Gateway网关解决跨域问题生产部署时把前端打包后的dist目录放进Nginx由Nginx统一转发到网关服务。这一套下来前端和后端的部署完全解耦独立发版互不影响。Vue项目里像请求库封装、路由守卫全局拦截、状态管理集中存放用户信息这些属于基本功但每一样都是联调时不踩坑的保证。5.2 ECharts可视化大屏与交互图表可视化是整个项目最直观的成果展示。我接入了ECharts做一套“商品推荐数据大屏”包含四类图表销量Top10商品柱状图展示热门商品的销量和加购转化率用户活跃时段折线图透出用户在什么时间点最活跃帮助运营决定推荐位配置策略商品分类分布饼图展示不同品类商品占比推荐点击漏斗图从曝光到点击再到加购和下单每一层的转化率一目了然。大屏的数据来源是推荐服务提供的汇总统计接口前端定时拉取轮询刷新。这里要提醒一个开发细节ECharts实例在动态更新数据时一定要先调用setOption中的notMerge参数或者主动清空旧数据否则新旧数据叠加图表会变得很乱。我在开发中遇到过两三次这种问题排查到最后都是数据合并导致的渲染异常。5.3 前后端鉴权与接口安全设计防爬这个问题做这个项目时我专门考虑过。前端的页面源码、接口地址都是暴露的如果被别人写个脚本直接抓接口系统数据很容易被盗。我在前端层面做了几个常规防护措施接口统一加签名参数时间戳、随机数和密钥通过哈希算法生成签名后端校验签名合法性敏感接口增加图形验证码识别真实用户行为用户Token绑定设备信息检测到异常设备直接拒绝访问。后端Controller层也做了一层防爬拦截校验请求的User-Agent、请求频率和Referer来源不符合正常浏览器特征的请求直接丢弃。这套防护不能说绝对安全但能挡住大多数脚本触发式的爬取行为维持系统和数据的基本安全边界。6. 部署、性能调优与那几个让我熬夜解决的问题6.1 Docker Compose部署一套微服务全家桶项目交付时的部署方案我选的是Docker Compose。虽然流水线上常推Kubernetes但考虑到实际运维成本和团队接受度Docker Compose在中小型项目里已经非常可靠。我用Docker编排了MySQL、Redis、Nacos、Kafka、Spark和六个业务服务每个服务一个容器配置挂载在统一的.env文件里一条docker-compose up -d命令就能拉起全套环境。有一个细节值得注意容器之间的网络通信要用服务名而非IP地址。刚开始我图省事在配置里写了具体的容器IP结果每次重启IP变化服务之间全部失联排查了很久才发现是动态IP导致的问题。改成服务名访问后这坑就彻底消失了。对于多服务部署建议启动顺序固定为中间件MySQL、Redis、Kafka → Nacos → 业务服务依赖关系要清晰否则经常出现服务启动时注册不上Nacos的问题。6.2 性能调优缓存、索引与超时的平衡系统压测时暴露了不少性能问题集中调优了三个方向。第一是缓存策略。推荐结果做了两级缓存热点用户的推荐列表缓存到RedisTTL设置5分钟商品详情缓存在本地CaffeineTTL设置5分钟。两个缓存都设置过期和主动刷新机制避免缓存穿透。第二是数据库索引。行为表的数据量涨得很快我在user_id和goods_id上建立了联合索引在event_time上建立单列索引行为查询的速度从原来的1.8秒降到120毫秒左右。第三是连接池和超时参数的统一。数据库连接池、Redis连接池、Feign调用的最大连接数和超时时间都根据压测结果做了调整——超时时间不宜过长否则线程容易被拖死3秒是一个比较稳妥的默认值。6.3 踩坑实录从Kafka重复消费到Nacos配置覆盖最后聊聊让我印象最深的三个坑每一个都花了我大半天时间写出来给大家提个醒。第一个坑是Kafka消费者组的重复消费问题。系统上线后我发现推荐记录里有一些异常重复排查到Kafka消费者在发生Rebalance时没有提交offset的消息会被重新消费导致行为数据重复写入。解决办法是调整消费端参数enable.auto.commitfalse改为业务逻辑处理成功后手动提交offset并且把消费者的max.poll.interval.ms调大避免业务处理超时触发Rebalance。第二个坑是Nacos配置覆盖导致的“灵异Bug”。某个服务死活连接不上MySQL报错信息却显示连接串还是旧的地址。查了半天发现Nacos中有一个默认配置组里也存放了MySQL连接串它覆盖了我在项目配置文件中写的正确值。Nacos配置的优先级高于本地配置这个机制如果不了解很容易被它坑到。后续我统一规范了配置命名空间把环境隔离做好这类问题再没出现过。第三个坑是Spark模型训练时的内存溢出。直接用默认配置跑ALS训练集群直接OOM控制台一片红色。调优方案是给Spark作业单独配置executor内存和堆积内存并调整ALS的分区数让数据更均匀地分布到各executor上。最终我把executor内存设为2G、分区数调整到与数据量匹配之后训练任务稳定跑完。这一套项目做下来我的体会是商品推荐系统不是一个“务虚”的技术拼盘它是一整条数据链路从采集、清洗、计算、服务化到可视化展示的完整闭环。每一个环节都有它自己的坑和最优解而微服务与大数据的组合本质是为了让这条链路在数据量和并发量增长时依然可扩展、可维护。如果你正准备动手做类似的项目我建议不要贪多先跑通一条最小闭环——比如只做一个商品服务和推荐服务前端只展示一个列表——再逐步引入Kafka、Spark、Flink这些组件。链路越短问题越容易定位跑通了主干枝叶再一点点舒展。