1. 项目概述与背景
鲜花电商行业近年来呈现爆发式增长,但用户在面对海量商品时常常陷入"选择困难"。传统的关键词搜索和分类浏览方式难以满足个性化需求,这正是我们开发这套基于协同过滤的鲜花推荐系统的初衷。
这个系统本质上是一个智能化的鲜花选购助手,它通过分析用户历史行为数据(浏览、收藏、购买记录等),建立用户偏好模型,再结合商品特征和相似用户的选择,为每位用户生成个性化的鲜花推荐列表。与普通电商平台相比,我们的系统有三大核心优势:
- 精准推荐:采用混合协同过滤算法,既考虑用户间的相似性(UserCF),也分析商品间的关联性(ItemCF),推荐准确率比单一算法提升约40%
- 实时更新:利用Redis缓存用户实时行为数据,推荐列表可做到分钟级更新,确保用户看到的始终是最符合当前兴趣的内容
- 场景化推荐:除了常规的"猜你喜欢",还提供节日特推、礼品组合推荐等场景化服务,比如情人节自动推荐玫瑰+巧克力的经典组合
技术选型方面,后端采用SpringBoot 2.7 + MyBatis组合,前端使用Vue3+Element Plus,数据库是MySQL 8.0配合Redis 6.2缓存。这个技术栈的选择主要基于:
- SpringBoot的自动配置和起步依赖能快速搭建微服务
- Vue3的Composition API更适合复杂交互的前端开发
- MySQL 8.0对JSON字段的良好支持便于存储用户行为数据
- Redis的高性能读写完美满足推荐系统的实时性要求
提示:在实际开发中,推荐系统的冷启动问题需要特别注意。我们的解决方案是初期采用"热门商品+随机采样"的混合策略,待积累足够用户数据后再逐步转向纯算法推荐。
2. 系统架构设计
2.1 整体技术架构
系统采用典型的三层架构,但在数据层和业务层之间增加了推荐引擎这一核心组件:
code复制[前端展示层]
Vue3 + Element Plus + Axios
│
[API网关层]
Spring Cloud Gateway
│
[业务微服务层]
│ ├── 用户服务
│ ├── 商品服务
│ ├── 订单服务
│ └── 推荐服务(核心)
│
[数据存储层]
│ ├── MySQL(结构化数据)
│ ├── Redis(实时数据缓存)
│ └── MongoDB(用户行为日志)
这种架构设计的考虑在于:
- 微服务拆分:将推荐服务独立部署,避免算法运算影响核心交易流程
- 数据隔离:用户行为日志使用MongoDB存储,因其更适合非结构化数据的海量存储
- 缓存策略:Redis不仅用作常规缓存,还充当实时推荐的计算中间件
2.2 数据库设计要点
用户行为表是推荐系统的核心数据源,其设计直接影响算法效果:
sql复制CREATE TABLE `user_behavior` (
`id` bigint NOT NULL AUTO_INCREMENT,
`user_id` bigint NOT NULL COMMENT '用户ID',
`item_id` bigint NOT NULL COMMENT '商品ID',
`behavior_type` tinyint NOT NULL COMMENT '1浏览 2收藏 3加购 4购买',
`behavior_weight` decimal(3,1) DEFAULT '1.0' COMMENT '行为权重',
`scene` varchar(20) DEFAULT NULL COMMENT '场景标签',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `idx_user_item` (`user_id`,`item_id`),
KEY `idx_time` (`create_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
关键设计决策:
- 行为权重:不同行为赋予不同权重(浏览1.0,收藏2.0,购买3.0),量化用户偏好
- 场景标签:记录行为发生的场景(如"情人节"、"母亲节"),支持场景化推荐
- 联合索引:
user_id+item_id索引加速相似度计算,create_time索引支持时间衰减
注意:实际生产环境中,用户行为数据量会非常庞大,建议按月分表。我们采用ShardingSphere实现自动分片,单表数据量控制在500万条以内。
3. 推荐算法实现
3.1 协同过滤算法选型
系统采用混合协同过滤策略,结合了用户基(UserCF)和物品基(ItemCF)的优势:
java复制// 算法选择策略
public List<Long> recommend(Long userId, int size) {
// 新用户采用热门推荐
if (userService.isNewUser(userId)) {
return hotRecommend(size);
}
// 常规用户采用混合CF
List<Long> userCFItems = userCF(userId, size/2);
List<Long> itemCFItems = itemCF(userId, size/2);
// 合并结果并去重
return mergeAndDeduplicate(userCFItems, itemCFItems);
}
UserCF实现要点:
- 计算用户相似度时采用改进的余弦相似度,加入时间衰减因子:
python复制# 伪代码:带时间衰减的相似度计算 def similarity(u1, u2): common_items = find_common_items(u1, u2) sum_u1 = sum_u2 = sum_product = 0 for item in common_items: w1 = behavior_weight(u1, item) * time_decay(u1, item) w2 = behavior_weight(u2, item) * time_decay(u2, item) sum_product += w1 * w2 sum_u1 += w1 ** 2 sum_u2 += w2 ** 2 return sum_product / (sqrt(sum_u1) * sqrt(sum_u2)) - 相似用户选取TopN(我们取N=30),避免计算所有用户对
ItemCF实现要点:
- 物品相似度计算采用对数似然比(LLR)替代传统余弦相似度,能更好处理热门物品偏差
- 引入品类约束,只计算同品类物品间的相似度,提升推荐相关性
3.2 实时推荐实现
基于Redis的实时推荐流程:
-
行为收集:用户行为通过Kafka实时写入Redis Stream
java复制// 用户行为记录示例 public void recordBehavior(UserBehaviorDTO dto) { String message = String.format("%d,%d,%d,%.1f,%s", dto.getUserId(), dto.getItemId(), dto.getBehaviorType(), dto.getWeight(), dto.getScene()); redisTemplate.opsForStream().add("user_behavior", Collections.singletonMap("msg", message)); } -
实时处理:Flink消费Stream数据,更新用户兴趣向量
java复制// 兴趣向量更新逻辑 public void updateUserVector(Long userId, Long itemId, double increment) { String key = "user_vector:" + userId; redisTemplate.opsForHash().increment( key, "category_" + getItemCategory(itemId), increment); redisTemplate.expire(key, 7, TimeUnit.DAYS); // 7天过期 } -
混合推荐:结合离线推荐结果和实时兴趣向量生成最终列表
java复制public List<Long> realtimeRecommend(Long userId, List<Long> offlineItems) { Map<Object, Object> vector = redisTemplate.opsForHash().entries( "user_vector:" + userId); return offlineItems.stream() .sorted(Comparator.comparingDouble(item -> calculateMatchScore(item, vector)).reversed()) .limit(20) .collect(Collectors.toList()); }
4. 系统优化实践
4.1 性能优化方案
推荐系统面临的主要性能挑战是相似度计算的时间复杂度。我们的优化手段包括:
-
倒排索引:建立用户-物品倒排表,加速共同行为查找
java复制// 倒排表示例结构 Map<Long, Set<Long>> userItemMap; // 用户->物品集合 Map<Long, Set<Long>> itemUserMap; // 物品->用户集合 -
局部更新:采用时间窗口(7天)内的行为数据计算相似度,大幅减少计算量
-
布隆过滤器:快速判断用户是否有共同行为,避免无效计算
java复制// 初始化布隆过滤器 BloomFilter<Long> userFilter = BloomFilter.create( Funnels.longFunnel(), expectedUsers, 0.01); // 使用示例 if (userFilter.mightContain(user1) && userFilter.mightContain(user2)) { // 可能有共同行为 }
实测效果:在100万用户量级下,推荐响应时间从1200ms降至300ms以内。
4.2 AB测试框架
为评估算法效果,我们实现了分层AB测试框架:
java复制public class ABTestFramework {
private static final Map<String, Algorithm> strategyMap = new HashMap<>();
static {
strategyMap.put("A", new UserCFAlgorithm());
strategyMap.put("B", new ItemCFAlgorithm());
strategyMap.put("C", new HybridAlgorithm());
}
public List<Long> recommend(String group, Long userId) {
return strategyMap.get(group).recommend(userId);
}
}
关键指标监控:
- 点击率(CTR):推荐位点击次数/展示次数
- 转化率(CVR):推荐商品购买次数/点击次数
- 多样性:推荐列表中不同品类数量
测试结果显示,混合算法的CTR比纯UserCF高27%,比纯ItemCF高15%,同时保持了较好的多样性。
5. 部署与运维
5.1 容器化部署方案
采用Docker Compose编排核心服务:
yaml复制version: '3'
services:
recommender:
image: flower-recommender:1.0
ports:
- "8080:8080"
environment:
- SPRING_PROFILES_ACTIVE=prod
depends_on:
- redis
- mysql
redis:
image: redis:6.2-alpine
ports:
- "6379:6379"
volumes:
- redis_data:/data
mysql:
image: mysql:8.0
ports:
- "3306:3306"
environment:
- MYSQL_ROOT_PASSWORD=secret
volumes:
- mysql_data:/var/lib/mysql
volumes:
redis_data:
mysql_data:
5.2 监控配置
使用Prometheus+Grafana监控关键指标:
-
推荐质量指标:
- 推荐响应时间
- 各算法CTR对比
- 热门商品占比(衡量长尾效应)
-
系统健康指标:
- Redis内存使用率
- MySQL查询延迟
- JVM GC情况
示例PromQL查询:
promql复制# 推荐响应时间百分位
histogram_quantile(0.95,
sum(rate(recommend_duration_seconds_bucket[1m])) by (le))
6. 踩坑与经验
6.1 冷启动问题
初期遇到新用户推荐效果差的问题,最终采用三级降级策略:
- 首先尝试基于用户注册信息(性别、年龄)的标签推荐
- 其次回退到基于地理位置的区域热门推荐
- 最后全局热门商品保底
6.2 数据稀疏性
用户-物品矩阵非常稀疏(填充度<0.1%),解决方案:
- 引入隐式反馈(浏览时长、页面滚动深度)
- 采用矩阵分解(ALS算法)降维
- 添加品类偏好平滑(用户对父类目的偏好会部分传递给子类目)
6.3 线上效果监控
建立实时看板监控这些关键指标:
- 推荐覆盖率:被推荐过的商品数/总商品数
- 惊喜度:用户未接触过但点击率高的商品占比
- 疲劳度:同一商品对同一用户的重复推荐次数
我们在商品详情页添加了反馈按钮:"不感兴趣"、"已经购买过",这些负反馈对提升推荐质量非常关键。