简介:收藏是内容专栏场景中高频且关键的基础能力,看似简单,实则涉及数据建模、幂等去重、高并发写入、缓存一致性等诸多工程细节。本文从需求拆解出发,完整讲解收藏功能的数据模型、接口设计、异步计数、一致性保障与风控统计方案,并辅以示意性代码,帮助开发者搭建稳定、可扩展、可反哺内容推荐体系的收藏服务。
在典型的内容平台中,专栏承载着长图文内容的创作与深度阅读。与时效性强的信息流不同,专栏内容的价值周期更长,用户“先收藏、后细读”的行为非常普遍。收藏功能因此成为连接用户与内容的长期纽带,其价值体现在三个层面:
在动手编码之前,先把需求边界定义清楚。一个完整的收藏能力通常包含以下功能点:
非功能目标则包括:不丢单、不重复、状态最终一致、计数可对账修复、接口可水平扩展。这五个词是后文所有设计决策的主线。
核心数据通常拆分为三张表,职责清晰、互不耦合:
1. 收藏关系表:记录“谁收藏了什么”,是业务事实表。
CREATE TABLE favorite_record (id BIGINT PRIMARY KEY,user_id BIGINT NOT NULL,content_id BIGINT NOT NULL,folder_id BIGINT NOT NULL DEFAULT 0,status TINYINT NOT NULL DEFAULT 1, -- 1有效 0已取消created_at DATETIME NOT NULL,updated_at DATETIME NOT NULL,UNIQUE KEY uk_user_content (user_id, content_id),KEY idx_user_folder (user_id, folder_id, id));
2. 收藏夹表:承载用户的组织维度,包含夹 ID、用户 ID、名称、排序权重、内容数等字段。
3. 内容计数表:以 content_id 为主键维护收藏总量,供详情页展示与内容分析使用。
几个关键设计决策值得展开:
(user_id, content_id) 的唯一约束是防重的最后一道防线,即使上层逻辑出现并发漏洞,数据库也能保证同一用户对同一内容只有一条有效记录;对外建议将收藏与取消收藏合并为一个语义化接口,减少接口数量并统一鉴权与限流入口:
POST /api/v1/favorite/action{"content_id": 123456,"folder_id": 0,"action": "add" // add / cancel}
服务端处理流程可概括为六步:鉴权与参数校验 → 幂等判断 → 写主存储 → 投递计数事件 → 失效相关缓存 → 返回结果。示意代码如下:
func FavoriteAction(ctx context.Context, uid, cid, fid int64, action string) error {switch action {case "add":err := db.InsertFavorite(uid, cid, fid)if IsDuplicateKeyErr(err) {return nil // 幂等:重复收藏视为成功}if err != nil {return err}mq.Publish(CountEvent{ContentID: cid, Delta: +1})case "cancel":if err := db.UpdateFavoriteStatus(uid, cid, 0); err != nil {return err}mq.Publish(CountEvent{ContentID: cid, Delta: -1})}cache.Delete(UserFavKey(uid, cid)) // 失效收藏状态缓存return nil}
这里的幂等处理利用了数据库唯一键:并发请求下,第二个插入会触发冲突错误,业务层将其解释为“已收藏,直接成功”,从而保证用户无论点击多少次,结果都是确定的。
1. 缓存分层设计。 收藏状态的判断是典型的读多写少场景,适合采用“分布式缓存 + 回源”的读路径:以用户维度加内容维度构造键,命中直接返回,未命中回源数据库并回填,同时设置随机化过期时间防止缓存雪崩。收藏列表则可缓存用户最近一页的内容 ID 序列,详情信息再通过批量接口按需获取,避免大对象长期占用缓存。
2. 计数异步化与削峰。 热门专栏发布后可能瞬间涌入大量收藏,若每次都直接对计数表执行累加更新,单行记录会成为锁热点。通用解法有两条路径:其一,先在缓存中对计数进行原子累加,由定时任务批量刷回数据库;其二,将计数事件投递到消息队列,由消费端聚合并周期性落库。两类方案都需要与“计数对账”配套使用,确保最终准确。若使用云上基础设施,可直接复用云厂商提供的消息队列、缓存与监控告警等通用产品能力,例如百度智能云在此类场景下即提供了相应的托管组件,开发者无需自建整套链路。
3. 列表查询用游标分页。 传统 LIMIT offset, size 在深翻页时性能急剧劣化,应改为基于游标(通常用主键 ID 或时间戳)的续读方式:
SELECT content_id, folder_id, created_atFROM favorite_recordWHERE user_id = ? AND status = 1 AND id < ?ORDER BY id DESCLIMIT 20;
该写法无论翻到多深,扫描成本都是恒定的,配合索引可以稳定支撑大体量用户的列表回访。
缓存与数据库的双写必然引入一致性挑战,实践中建议组合以下手段:
功能稳定之后,体验层面的打磨同样重要:支持自定义收藏夹与默认收藏夹,降低整理成本;重复收藏时给出“已存在”的轻提示而非报错;提供批量移动、批量删除等整理工具;对已失效或下线的内容在列表中做弱化标记;隐私上默认私密、可选公开,尊重用户对个人数据的控制权。这些细节虽小,却决定了收藏功能能否真正成为用户的“第二大脑”。
收藏数据不应只服务于功能本身。在数据侧,收藏率、收藏后回访率、收藏到完成阅读的转化率,都是评估内容长期价值的优质信号,应通过统一的行为埋点管道输出给推荐与分析系统。在风控侧,需要对单用户收藏频次做限流与频控,结合设备与行为特征识别异常刷量,防止收藏计数被污染进而影响内容评估的公正性。
收藏功能是“小切口、深工程”的典型案例:表层是两个按钮,底层却贯穿了数据建模、幂等设计、缓存策略、异步化、一致性对账与风控统计等完整链路。把握住“数据库为事实源、缓存做加速、计数走异步、对账保准确”这一主线,再结合自身业务的量级与增长预期做取舍,就能构建出一个既稳又快、还能持续反哺内容生态的收藏服务。