推荐系统架构实战:从千人一面到千人千面优化路径
时间:2026-08-15 14:55
推荐系统从热门推荐演变为协同过滤和深度学习,实现从千人一面到千人千面。系统架构分为召回、排序、输出三层,召回层采用多路并行策略,排序层通过特征工程与模型精确打分,提升转化率与长尾商品曝光。
# 推荐系统架构:从千人一面到千人千面
先从一个真实的电商案例讲起。
2019年,我们运营过一个电商平台,当时商品列表完全按照发布时间排序。用户想找到需要的商品,只能一页一页往后翻,整体购物体验非常差。至于转化率?只有1%。
更严重的是,平台里的大量长尾商品几乎没有曝光机会,流量高度集中在少数热门商品上,资源浪费非常明显。
后来我们上线了推荐系统,平台情况才出现了根本性改善。首页开始提供个性化推荐,同时增加了“相关商品推荐”“猜你喜欢”等常见推荐功能。结果也非常直接:转化率提升到3%,几乎增长了三倍。
这背后其实体现了推荐系统最核心的商业价值:它绝不只是一个提升体验的小功能,而是平台的流量分发中枢,也是决定业务能否持续增长的重要能力。

## 推荐系统演进
推荐系统的发展并不是一步到位的,它的演进路径,实际上反映了推荐算法、系统架构与业务需求持续博弈和升级的过程。
### 阶段一:热门推荐
这是最早期、也最容易落地的推荐方式。
```text
┌─────────────────────────────────────────────────────────────────┐
│推荐系统 - 阶段一 │
│ │
│所有用户 → 相同推荐结果 │
│ │
│推荐逻辑: │
│1. 按销量排序 │
│2. 取Top 100 │
│3. 随机打乱展示 │
│ │
│问题: │
│- 千人一面,无个性化 │
│- 马太效应,热门越热 │
│- 长尾商品无曝光 │
│ │
└──────────────────────────────────────────────────────────────────┘
```
这个阶段的推荐逻辑非常直接:所有用户看到的内容都一样,系统先按照销量排序,再取Top 100,最后做一次随机打散展示。问题也很突出:没有任何个性化能力,热门商品会越来越热门,长尾商品则几乎彻底失去展示机会。
### 阶段二:协同过滤
冷启动问题,几乎是每个推荐系统工程师都会遇到的经典难题,而协同过滤正是在这样的背景下成为主流方案之一。
```text
┌─────────────────────────────────────────────────────────────────┐
│推荐系统 - 阶段二 │
│ │
│用户A → 相似用户 → 推荐用户A未购买的商品 │
│ │
│User-based CF: │
│1. 找到和用户A相似的用户 │
│2. 推荐相似用户喜欢的商品 │
│ │
│Item-based CF: │
│1. 找到用户A喜欢的商品 │
│2. 推荐和这些商品相似的商品 │
│ │
│优点: │
│- 千人千面 │
│- 推荐效果提升 │
│ │
│问题: │
│- 冷启动问题 │
│- 数据稀疏问题 │
│ │
└──────────────────────────────────────────────────────────────────┘
```
协同过滤让推荐系统真正迈入“个性化推荐”阶段。基于用户的协同过滤,本质上是先找到与你行为相似的用户,再把他们喜欢的商品推荐给你;基于物品的协同过滤,则是从你已经喜欢的商品出发,继续推荐相似商品。虽然推荐效果明显提升,但冷启动和数据稀疏这两个关键问题也随之暴露——新用户和新商品缺少行为数据,系统往往难以准确推荐。
### 阶段三:深度学习推荐
随着深度学习技术成熟,推荐系统进入了更智能、更复杂的新阶段。
```text
┌─────────────────────────────────────────────────────────────────┐
│推荐系统 - 阶段三 │
│ │
│用户 → 特征工程 → 深度学习模型 → 推荐结果 │
│ │
│模型: │
│- Wide & Deep │
│- DeepFM │
│- DIN/DIEN │
│ │
│特征: │
│- 用户特征:年龄、性别、历史行为 │
│- 商品特征:类目、品牌、价格 │
│- 上下文特征:时间、地点、设备 │
│ │
│优点: │
│- 推荐效果大幅提升 │
│- 自动学习特征组合 │
│ │
└──────────────────────────────────────────────────────────────────┘
```
深度学习推荐模型,例如Wide & Deep、DeepFM、DIN、DIEN等,不再高度依赖人工规则,而是通过大规模数据自动学习复杂的特征交互关系。用户特征、商品特征、场景上下文特征都可以输入模型,从而显著提升推荐精度和点击转化表现。不过,与效果提升相伴而来的,是更高的系统复杂度、计算成本以及模型训练与维护成本。
## 推荐系统架构
### 整体架构
一个成熟的推荐系统架构,通常可以分为三层:召回层、排序层和输出层。
```text
┌─────────────────────────────────────────────────────────────────┐
│推荐系统整体架构 │
│ │
│┌─────────────────────────────────────────────────────────┐ │
││用户请求 │ │
│└─────────────────────────────────────────────────────────┘ │
│↓ │
│┌─────────────────────────────────────────────────────────┐ │
││召回层 │ │
││协同过滤 → 向量召回 → 热门召回 → 规则召回 │ │
││ ↓ │ │
││召回1000个候选商品 │ │
│└─────────────────────────────────────────────────────────┘ │
│↓ │
│┌─────────────────────────────────────────────────────────┐ │
││排序层 │ │
││粗排 → 精排 → 重排 │ │
││ ↓ ↓ ↓ │ │
││500 100 20 │ │
│└─────────────────────────────────────────────────────────┘ │
│↓ │
│┌─────────────────────────────────────────────────────────┐ │
││输出层 │ │
││去重 → 过滤 → 打乱 → 返回 │ │
│└─────────────────────────────────────────────────────────┘ │
│ │
└──────────────────────────────────────────────────────────────────┘
```
当用户发起请求后,首先进入召回层。召回层的核心目标是“查全”,也就是从海量商品库中快速找出用户可能感兴趣的一批候选商品,例如1000个。接着进入排序层,重点解决“查准”问题——粗排使用轻量模型快速筛选,精排利用更复杂的模型做精准打分,重排则进一步加入多样性、去重、业务规则等策略,最后输出大约20个推荐结果。输出层则负责最终的结果整理、过滤与返回。
### 召回层设计
“多路召回”是当前推荐系统架构中最常见、也最实用的方案。不同召回策略并行执行,各自返回一部分候选商品,最后统一合并。这样做的优势是能够从多个维度覆盖用户兴趣,提高召回覆盖率。
```ja va
@Service
public class RecallService {
@Autowired
private CollaborativeFilteringRecall cfRecall;
@Autowired
private VectorRecall vectorRecall;
@Autowired
private HotRecall hotRecall;
public List
recall(Long userId, int size) {
List>> futures = new ArrayList<>();
// 1. 协同过滤召回
futures.add(CompletableFuture.supplyAsync(() -> cfRecall.recall(userId, 200)));
// 2. 向量召回
futures.add(CompletableFuture.supplyAsync(() -> vectorRecall.recall(userId, 300)));
// 3. 热门召回(兜底)
futures.add(CompletableFuture.supplyAsync(() -> hotRecall.recall(userId, 500)));
// 4. 规则召回(新品)
futures.add(CompletableFuture.supplyAsync(() -> ruleRecall.recall(userId, 100)));
// 5. 合并结果
Set productIds = new LinkedHashSet<>();
for (CompletableFuture> future : futures) {
try {
productIds.addAll(future.get(100, TimeUnit.MILLISECONDS));
} catch (Exception e) {
log.warn("召回失败", e);
}
}
return new ArrayList<>(productIds).subList(0, Math.min(size, productIds.size()));
}
}
```
这段代码比较直观地展示了多路召回的典型实现方式:并行执行、超时容错、统一合并。协同过滤负责挖掘个性化兴趣,向量召回负责语义相似性匹配,热门召回承担兜底作用,规则召回则可以满足新品曝光或运营干预等需求。
### 排序层设计
完成召回之后,下一步就是排序。排序层通常会使用更复杂的推荐模型,对候选商品做精细化打分和排序。
```ja va
@Service
public class RankService {
@Autowired
private RankModelService rankModelService;
public List rank(Long userId, List productIds) {
// 1. 获取特征
List features = featureService.getFeatures(userId, productIds);
// 2. 模型预测
List rankedProducts = new ArrayList<>();
for (ProductFeatures feature : features) {
float score = rankModelService.predict(feature);
rankedProducts.add(new RankedProduct(feature.getProductId(), score));
}
// 3. 排序
rankedProducts.sort((a, b) -> Float.compare(b.getScore(), a.getScore()));
return rankedProducts;
}
}
```
排序层的核心在于特征工程与模型预测。特征越全面,模型能力越强,推荐排序通常也越精准。但同时要注意性能问题:排序阶段往往需要对数百个候选商品逐个计算分数,因此接口延迟必须被严格控制,通常要在几十毫秒内完成。
## 推荐算法详解
### 协同过滤
协同过滤是推荐算法中最经典的方案之一。这里重点看一下基于用户的协同过滤实现思路。
```ja va
@Service
public class UserCFRecall {
public double userSimilarity(Long userA, Long userB) {
Set itemsA = getUserItems(userA);
Set itemsB = getUserItems(userB);
// Jaccard相似度
Set intersection = new HashSet<>(itemsA);
intersection.retainAll(itemsB);
Set union = new HashSet<>(itemsA);
union.addAll(itemsB);
return (double) intersection.size() / union.size();
}
public List findSimilarUsers(Long userId, int topK) {
Map similarities = new HashMap<>();
for (Long otherUserId : getAllUsers()) {
if (!otherUserId.equals(userId)) {
similarities.put(otherUserId, userSimilarity(userId, otherUserId));
}
}
return similarities.entrySet().stream()
.sorted(Map.Entry.comparingByValue().reversed())
.limit(topK)
.map(Map.Entry::getKey)
.collect(Collectors.toList());
}
public List recommend(Long userId, int size) {
List similarUsers = findSimilarUsers(userId, 50);
Set userItems = getUserItems(userId);
Set recommendItems = new HashSet<>();
for (Long similarUser : similarUsers) {
Set items = getUserItems(similarUser);
items.removeAll(userItems);
recommendItems.addAll(items);
}
return new ArrayList<>(recommendItems).subList(0, Math.min(size, recommendItems.size()));
}
}
```
这个实现思路比较清晰:先使用Jaccard相似度计算用户之间的相似程度,再找出Top 50相似用户,最后把这些相似用户买过、但当前用户还没有买过的商品作为推荐结果。虽然实现不复杂,但在行为数据较丰富的场景下,协同过滤的效果依然很有竞争力。
### 向量召回
向量召回已经成为当前推荐系统中最主流的召回方式之一。它的本质,是为每个用户和商品生成向量表示,再通过向量检索快速找到最相似的商品集合。
```ja va
@Service
public class VectorRecallService {
@Autowired
private VectorStore vectorStore; // Faiss/Milvus
public List recall(Long userId, int size) {
float[] userVector = getUserVector(userId);
List results = vectorStore.search(
userVector, size, "product_index");
return results.stream()
.map(SearchResult::getId)
.collect(Collectors.toList());
}
private float[] getUserVector(Long userId) {
// 方案1:从缓存获取
float[] vector = userVectorCache.get(userId);
if (vector != null) {
return vector;
}
// 方案2:实时计算
List beha viors = getUserBeha viors(userId);
vector = userModel.predict(beha viors);
// 缓存
userVectorCache.put(userId, vector);
return vector;
}
}
```
向量召回的关键点在于:如何生成高质量的用户向量和商品向量。通常需要借助深度学习模型,根据用户历史行为序列、商品内容特征等信息进行编码。向量存储与检索层则通常依赖Faiss、Milvus等高性能ANN近似最近邻检索引擎。
## 实时推荐
推荐系统不能只依赖离线计算,实时推荐能力往往决定了最终体验上限。
### 实时特征更新
用户行为是持续实时发生的,比如点击、加购、收藏、购买等,这些信号最好能在秒级内反馈到推荐结果中。
```ja va
@Service
public class UserBeha viorProcessor {
@Autowired
private FeatureStore featureStore;
@KafkaListener(topics = "user-beha vior")
public void onMessage(UserBeha viorEvent event) {
RealTimeFeatures features = featureStore.get(event.getUserId());
switch (event.getType()) {
case CLICK:
features.addClick(event.getProductId(), event.getTimestamp());
break;
case PURCHASE:
features.addPurchase(event.getProductId(), event.getAmount());
break;
case CART:
features.addCart(event.getProductId());
break;
}
featureStore.put(event.getUserId(), features);
// 如果用户正在浏览推荐页,触发重排
if (isUserBrowsingRecommend(event.getUserId())) {
recommendService.refreshRecommend(event.getUserId());
}
}
}
```
通过Kafka消费用户行为事件流,系统可以实时更新用户特征,并在必要时主动刷新推荐结果。这类实时特征更新机制,是构建实时推荐系统的重要基础设施。
### 实时推荐API
一个典型的推荐接口,通常需要串联召回、排序和重排等多个核心环节。
```ja va
@RestController
@RequestMapping("/api/recommend")
public class RecommendController {
@Autowired
private RecommendService recommendService;
@GetMapping("/home")
public Result> homeRecommend(
@RequestHeader("X-User-Id") Long userId,
@RequestParam(defaultValue = "20") int size) {
UserFeatures features = featureService.getFeatures(userId);
List productIds = recallService.recall(userId, 1000);
List ranked = rankService.rank(userId, productIds);
List products = rerankService.rerank(userId, ranked, size);
return Result.success(products);
}
@GetMapping("/related/{productId}")
public Result> relatedRecommend(
@PathVariable Long productId,
@RequestParam(defaultValue = "20") int size) {
List products = recommendService.getRelatedProducts(productId, size);
return Result.success(products);
}
}
```
首页个性化推荐和商品详情页相关推荐,是推荐系统最常见的两类业务场景。虽然整体流程都包括召回、排序和重排,但两者在召回策略、特征设计和排序模型上通常会有所区别。
## 踩坑实录
推荐系统在工程落地过程中,往往比算法本身更容易踩坑。下面分享几个最常见的问题。
### 坑1:冷启动问题
**问题**:新用户和新商品缺少行为数据,系统难以给出有效推荐。
**解决方案**:
```ja va
// 新用户冷启动
public List coldStartUserRecommend(Long userId) {
// 1. 基于注册信息推荐
UserInfo user = userService.getUser(userId);
List products = productMapper.selectByCategory(user.getInterestCategory());
// 2. 热门推荐兜底
if (products.size() < 20) {
products.addAll(hotRecall.recall(userId, 20 - products.size()));
}
return products;
}
// 新商品冷启动
public void handleNewProduct(Product product) {
// 1. 计算商品向量(基于类目、品牌、标题)
float[] vector = productEncoder.encode(product);
// 2. 找相似商品
List similarProducts = vectorStore.search(vector, 10);
// 3. 推荐给相似商品的购买用户
for (Long similarProductId : similarProducts) {
List users = getBuyers(similarProductId);
recommendNewProductToUsers(users, product.getId());
}
}
```
冷启动的核心思路是:先利用注册信息、热门商品、商品属性等非行为数据做基础推荐,等用户或商品逐渐积累起足够的行为数据后,再切换到协同过滤、向量召回或深度学习模型等更精准的推荐策略。
### 坑2:数据稀疏问题
**问题**:用户行为数据过于稀疏,导致协同过滤推荐效果不稳定。
**解决方案**:混合推荐
```ja va
public List hybridRecommend(Long userId, int size) {
double cfWeight = 0.4; // 协同过滤
double vectorWeight = 0.3; // 向量召回
double hotWeight = 0.2; // 热门推荐
double ruleWeight = 0.1; // 规则推荐
Map scores = new HashMap<>();
// 加权合并
for (Long productId : cfRecall.recall(userId, 200)) {
scores.merge(productId, cfWeight, Double::sum);
}
for (Long productId : vectorRecall.recall(userId, 200)) {
scores.merge(productId, vectorWeight, Double::sum);
}
// ... 其他路
return scores.entrySet().stream()
.sorted(Map.Entry.comparingByValue().reversed())
.limit(size)
.map(Map.Entry::getKey)
.collect(Collectors.toList());
}
```
混合推荐并不是把多个策略简单拼接,而是通过合理的权重设计,让不同推荐算法彼此补充。协同过滤擅长挖掘个性化兴趣,向量召回更偏向内容与语义相似,热门推荐提供稳定兜底,规则推荐则适合满足运营和业务控制需求。
### 坑3:实时性要求高
**问题**:用户行为一旦变化,推荐结果就需要尽快同步更新。
**解决方案**:流式处理
```ja va
// Flink实时处理用户行为
DataStream beha viorStream = env
.addSource(new KafkaSource("user-beha vior"))
.keyBy(UserBeha vior::getUserId)
.process(new ProcessFunction() {
@Override
public void processElement(UserBeha vior beha vior, Context ctx,
Collector out) {
// 更新用户特征
userFeatureStore.update(beha vior);
// 触发推荐刷新
out.collect(new RecommendUpdate(beha vior.getUserId()));
}
});
```
使用Flink等流式计算框架处理用户行为数据,可以实现秒级甚至更低延迟的特征更新和推荐刷新能力。不过,这也意味着系统需要投入更多基础设施建设成本与运维成本。
### 坑4:推荐多样性差
**问题**:推荐结果过度集中在少数类目,用户容易产生审美疲劳,体验下降。
**解决方案**:重排多样性控制
```ja va
public List diversityRerank(List products, int size) {
Map categoryCount = new HashMap<>();
List result = new ArrayList<>();
for (RankedProduct rp : products) {
String category = rp.getProduct().getCategory();
int count = categoryCount.getOrDefault(category, 0);
if (count < size / 3) {
result.add(rp.getProduct());
categoryCount.put(category, count + 1);
}
if (result.size() >= size) break;
}
return result;
}
```
多样性控制的核心,是在原始排序结果的基础上增加约束,例如限制单一类目展示数量,避免推荐结果过于同质化。虽然这样做可能会牺牲一部分短期点击率,但通常能显著提升用户长期体验与平台内容生态健康度。
## 总结
推荐系统核心要点:
| 阶段 | 方法 | 目的 |
|------|------|------|
| 召回 | 多路召回 | 查全率 |
| 排序 | 精排模型 | 查准率 |
| 重排 | 多样性控制 | 用户体验 |
**血的教训:**
推荐系统本质上是平台的流量分发器。好的推荐系统,能够精准连接用户需求与商品供给,显著提升点击率、转化率和用户留存;差的推荐系统,则可能让用户陷入信息茧房,甚至导致体验持续下滑。做推荐系统架构与推荐算法,技术当然重要,但真正决定效果上限的,始终是对用户和业务的深度理解。
*个人观点,仅供参考*