运动场馆预订平台在近几年呈现爆发式增长,但大多数平台仅提供基础的场馆信息展示和简单预订功能。在实际使用中,用户常常面临"选择困难症"——面对数十家场馆和上百种服务项目时,很难快速找到真正符合自己需求的场馆。这正是我们开发这个基于协同过滤算法的运动场馆服务平台的初衷。
这个平台的核心创新点在于:
提示:在实际开发中我们发现,单纯的协同过滤算法在冷启动阶段效果不佳,因此采用了混合推荐策略(后文会详细说明解决方案)
系统采用经典的SpringBoot分层架构:
code复制客户端层(Web/App)
↓
API网关(Spring Cloud Gateway)
↓
业务微服务(SpringBoot) ←→ 推荐服务(Python)
↓
数据持久层(MySQL + Redis)
↓
基础设施(阿里云ECS + RDS)
关键技术选型考量:
sql复制CREATE TABLE `user` (
`id` bigint NOT NULL AUTO_INCREMENT,
`username` varchar(50) NOT NULL,
`sport_preferences` json DEFAULT NULL, -- 存储用户运动偏好
PRIMARY KEY (`id`)
);
CREATE TABLE `venue` (
`id` bigint NOT NULL AUTO_INCREMENT,
`name` varchar(100) NOT NULL,
`sport_types` json NOT NULL, -- 场馆支持的运动类型
`features` json DEFAULT NULL, -- 场馆特征向量
PRIMARY KEY (`id`)
);
CREATE TABLE `user_behavior` (
`id` bigint NOT NULL AUTO_INCREMENT,
`user_id` bigint NOT NULL,
`venue_id` bigint NOT NULL,
`behavior_type` tinyint NOT NULL COMMENT '1-浏览 2-收藏 3-预订',
`weight` decimal(3,2) DEFAULT '1.00',
`create_time` datetime NOT NULL,
PRIMARY KEY (`id`)
);
我们采用混合推荐策略解决不同场景下的推荐需求:
基于用户的协同过滤(UserCF)
code复制用户相似度 = cos(u1, u2) = (u1·u2)/(||u1||*||u2||)
推荐得分 = Σ(相似用户对该场馆的评分)
基于物品的协同过滤(ItemCF)
code复制场馆相似度 = 同时喜欢i和j的用户数 / sqrt(喜欢i的用户数 * 喜欢j的用户数)
冷启动解决方案
为提高推荐实时性,我们设计了双通道数据处理流程:
code复制用户行为产生 → Kafka消息队列
↓
实时处理(Spark Streaming) → 更新Redis用户画像
↓
推荐服务读取最新画像 → 生成推荐结果
关键代码片段(Python实现):
python复制def calculate_user_similarity(user1, user2):
# 获取用户行为向量
vec1 = get_user_vector(user1)
vec2 = get_user_vector(user2)
# 计算余弦相似度
dot_product = np.dot(vec1, vec2)
norm1 = np.linalg.norm(vec1)
norm2 = np.linalg.norm(vec2)
return dot_product / (norm1 * norm2)
def recommend_for_user(user_id, top_n=10):
# 获取相似用户
similar_users = find_similar_users(user_id)
# 聚合推荐结果
recommendations = defaultdict(float)
for sim_user, similarity in similar_users:
for venue_id, rating in get_user_ratings(sim_user):
recommendations[venue_id] += similarity * rating
# 返回TopN推荐
return sorted(recommendations.items(), key=lambda x: x[1], reverse=True)[:top_n]
SpringBoot核心控制器实现:
java复制@RestController
@RequestMapping("/api/recommend")
public class RecommendController {
@Autowired
private RecommendService recommendService;
@GetMapping("/forUser")
public Result<List<VenueDTO>> getRecommendations(
@RequestHeader("userId") Long userId,
@RequestParam(defaultValue = "10") int size) {
// 获取推荐结果
List<Long> venueIds = recommendService.getUserRecommendations(userId, size);
// 查询场馆详情
List<VenueDTO> venues = venueService.batchGetVenue(venueIds);
return Result.success(venues);
}
}
为评估推荐效果,我们实现了以下指标监控:
点击通过率(CTR)
java复制// 记录推荐曝光和点击
@Aspect
@Component
public class RecommendTrackAspect {
@AfterReturning(pointcut = "execution(* com..RecommendController.getRecommendations(..))",
returning = "result")
public void trackExposure(JoinPoint jp, Object result) {
// 记录曝光日志
logService.logExposure(getCurrentUserId(), getRecommendVenueIds(result));
}
@AfterReturning("execution(* com..BookingController.createBooking(..))")
public void trackConversion() {
// 记录转化日志
logService.logConversion(getCurrentUserId(), getBookedVenueId());
}
}
推荐多样性指标
python复制def calculate_diversity(recommend_list):
venue_types = [v['sport_type'] for v in recommend_list]
return len(set(venue_types)) / len(venue_types)
采用多级缓存提升系统响应速度:
缓存更新策略:
java复制@Scheduled(cron = "0 0 3 * * ?") // 每天凌晨3点更新
public void refreshSimilarityMatrix() {
// 重新计算场馆相似度
Map<Long, Map<Long, Double>> matrix = recommendService.calculateVenueSimilarity();
// 更新Redis
redisTemplate.opsForHash().putAll("venue:similarity", matrix);
}
在实际运营中发现,不同行为应该赋予不同权重:
| 行为类型 | 初始权重 | 调整后权重 | 原因 |
|---|---|---|---|
| 浏览 | 1.0 | 0.8 | 兴趣度较低 |
| 收藏 | 1.0 | 1.5 | 强兴趣信号 |
| 预订 | 1.0 | 2.0 | 实际消费行为 |
| 取消 | - | -0.5 | 负面信号 |
实现代码:
java复制public double calculateBehaviorWeight(BehaviorType type, LocalDateTime time) {
double baseWeight = type.getBaseWeight();
// 时间衰减因子:最近的行为权重更高
long hours = ChronoUnit.HOURS.between(time, LocalDateTime.now());
double timeDecay = Math.exp(-hours / 72.0); // 半衰期3天
return baseWeight * timeDecay;
}
使用Docker Compose编排关键服务:
yaml复制version: '3.8'
services:
app:
image: venue-service:${VERSION}
ports:
- "8080:8080"
depends_on:
- redis
- mysql
environment:
- SPRING_PROFILES_ACTIVE=prod
recommend:
image: recommend-service:${PYTHON_VERSION}
ports:
- "5000:5000"
deploy:
resources:
limits:
cpus: '2'
memory: 2G
redis:
image: redis:6-alpine
ports:
- "6379:6379"
volumes:
- redis_data:/data
volumes:
redis_data:
Prometheus监控关键指标:
yaml复制# application.yml配置
management:
endpoints:
web:
exposure:
include: health,info,prometheus
metrics:
tags:
application: ${spring.application.name}
监控看板重点关注:
初期遇到的典型问题及解决方案:
问题:新用户没有行为数据,推荐结果随机
问题:新场馆曝光不足
数据稀疏性问题:
实时性要求:
AB测试框架:
java复制@GetMapping("/recommend")
public Result recommend(@RequestHeader("userId") Long userId) {
if (abTestService.isInGroup(userId, "new_algorithm")) {
return newAlgorithmRecommend(userId);
} else {
return oldAlgorithmRecommend(userId);
}
}
实际运营数据显示,采用混合推荐策略后: