个性化推荐不能用 strings.Contains,因其无用户行为、权重和历史偏好,需结合用户向量、热度衰减、已读降权等信号,用Bleve自定义排序实现,并规避协同过滤中的并发、索引、去重及超时问题。

直接用 strings.Contains 做推荐?别碰,它不解决“个性化”
个性化推荐不是“找出含关键词的文章”,而是“这个人更可能点哪几篇”。strings.Contains 只能做存在性判断,没用户行为、没权重、没历史偏好,结果千人一面。上线后你发现:新用户和老用户返回一模一样的列表,搜索“Go 并发”时,刚注册的运营和写了三年调度器的后端看到的排序完全一样——这不是搜索,是字符串回显。
真正要做的,是在匹配基础上叠加信号:
- 用户最近 7 天点击过的文章 ID 列表(缓存里查)
- 当前用户所属标签(如 “golang”、“系统编程”、“初学者”)
- 文章本身的热度衰减分(发布时间越近,基础分越高)
- 是否被该用户已读(已读文章降权,避免重复推荐)
用 Bleve + 用户向量做轻量级个性化排序
Bleve 是 Go 生态里最成熟的嵌入式全文搜索引擎,它原生支持自定义排序函数,比硬塞 sort.Slice 更可靠——因为排序发生在查询阶段,能利用索引剪枝,避免把全量匹配结果拉到内存再排。
实操要点:
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
立即学习“go语言免费学习笔记(深入)”;
- 建索引时,给每篇文章加一个
user_vector字段,类型为[]float64,存预计算好的用户兴趣向量(例如:[0.8, 0.1, 0.9] 表示强偏好 “并发” 和 “内存模型”) - 搜索时构造
bleve.NewSearchRequestOptions,调用sr.SortBy([]string{"-_score"})后,再用sr.Fields = []string{"title", "content"}控制返回字段 - 关键一步:在
SearchRequest的Explain开启后,可验证排序是否真的用了向量相似度——否则你写的SortFunc可能根本没触发 - 不要在
SortFunc里实时查 Redis 或 DB:Bleve 排序函数是同步执行的,IO 会卡死整个查询线程
协同过滤推荐必须绕开的三个坑
基于用户行为的协同过滤(UserCF)在 Go 里容易写,但线上一跑就出错。常见崩点不在算法本身,而在数据流和并发控制:
-
map[uint64][]uint64存用户相似度,但没加sync.RWMutex—— 多个 goroutine 同时写导致 panic: “concurrent map writes” - 从 MySQL 查用户最近 50 条浏览记录,用
SELECT * FROM user_actions WHERE uid = ? ORDER BY ts DESC LIMIT 50,但没给(uid, ts)加联合索引,慢查询拖垮整个搜索接口 - 相似用户列表取 top-5,但没去重:A 和 B 都相似,他们共同喜欢的文章 C 被推荐两次,前端展示重复卡片
- 冷启动用户(无行为)直接 fallback 到热门榜,但没设兜底超时 —— 热门榜服务抖动时,整个推荐请求 hang 住 3s+
调用百度 AI 或阿里云推荐 API 时的参数陷阱
用第三方推荐 API 看似省事,但 Go SDK 封装层常掩盖关键细节。比如调用阿里云 airec 的 /actions/recommend 接口:
- 必须传
X-Acs-Access-Token,但 Go SDK 默认不自动注入,得手动塞进request.Headers,否则返回InvalidAccessKeyId.NotFound -
body必须是 JSON 字符串,不是 struct;若用json.Marshal后直接赋值给request.Content,漏掉request.ContentType = "application/json",API 会静默返回空数组 - 用户 ID 字段名不是
uid,而是user_id(下划线),文档里写得模糊,但实际校验严格,错一个字符就 400 - 推荐结果里的
items是按服务端算好的分数排序,但这个分数不可见;如果你自己再按标题长度二次排序,等于把算法结果全搞乱
个性化不是加个“推荐”标签就完事。最易被忽略的是信号衰减——用户上周点的 “GC 原理” 文章,本周权重该降到 0.3,而不是还当核心偏好。这个逻辑没人帮你做,得你写在 scoreAdjust() 函数里,且必须和缓存过期时间对齐,否则推荐永远滞后三天。

















