收藏功能技术实现与架构设计指南
作者:很菜不狗2026.11.11 07:28浏览量:1简介:本文从收藏功能的核心价值出发,系统讲解其数据建模、接口设计、高并发写入、分页查询与缓存策略,并给出可落地的代码示例与避坑建议。读者可掌握一套可扩展、易维护的收藏系统设计方法,提升开发效率与系统稳定性。
收藏功能的技术实现与架构设计
收藏是内容型产品中最基础也最容易做“歪”的功能之一。看似只是“用户与内容之间的一条记录”,实际涉及写扩散与读扩散的取舍、计数一致性、分页稳定性、幂等设计以及冷热数据分离等一系列工程问题。本文从需求本质出发,逐层拆解收藏功能的数据模型、接口设计、并发控制与演进路径,给出一套可直接落地的技术方案。
一、需求本质与边界界定
收藏功能的表层需求是“让用户把感兴趣的内容存起来,之后能找到”。但拆解到技术层,至少包含四个子问题:
- 关系存储:谁收藏了什么,何时收藏,是否取消过。
- 状态查询:某条内容是否被当前用户收藏,用于前端按钮态渲染。
- 列表检索:按收藏时间倒序列出某用户的收藏内容,并支持分页。
- 计数统计:某条内容被多少人收藏,用于内容侧展示热度。
需要提前明确的边界包括:收藏是否支持分组/文件夹、是否允许重复收藏同一条内容、取消收藏后是否保留历史记录、收藏列表是否需要按内容类型过滤。这些边界直接决定数据模型的设计,建议在开发前与产品侧书面确认,避免后期返工。
二、数据模型设计
2.1 核心关系表
最朴素的做法是一张关系表:
CREATE TABLE user_favorite (id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,user_id BIGINT UNSIGNED NOT NULL,target_type TINYINT UNSIGNED NOT NULL COMMENT '内容类型:1文章 2视频 3商品',target_id BIGINT UNSIGNED NOT NULL,status TINYINT UNSIGNED NOT NULL DEFAULT 1 COMMENT '1有效 0已取消',created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,UNIQUE KEY uk_user_target (user_id, target_type, target_id),KEY idx_user_time (user_id, status, created_at)) ENGINE=InnoDB;
几个关键决策值得说明:
- 唯一索引兜底幂等:
uk_user_target保证同一用户对同一内容只有一条记录,重复收藏请求在数据库层被拦截,避免应用层并发导致的脏数据。 - 软删除而非物理删除:用
status字段标记取消,保留历史轨迹,便于风控与行为分析;若数据量敏感,也可改为物理删除并在归档表中留痕。 - 联合索引顺序:
idx_user_time以user_id打头、status居中、created_at收尾,正好覆盖“查某用户有效收藏按时间倒序”的主查询。
2.2 计数表的取舍
内容侧的收藏数有两种实现路径:
- 实时聚合:每次查询对关系表做
COUNT,实现简单但代价高,内容列表页会迅速打爆数据库。 - 独立计数:维护一张计数表或计数字段,写入时增减,查询时直接读取。
推荐后者,并采用“异步校准”策略:
CREATE TABLE target_favorite_count (target_type TINYINT UNSIGNED NOT NULL,target_id BIGINT UNSIGNED NOT NULL,fav_count BIGINT UNSIGNED NOT NULL DEFAULT 0,updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,PRIMARY KEY (target_type, target_id)) ENGINE=InnoDB;
写入侧通过 INSERT ... ON DUPLICATE KEY UPDATE fav_count = fav_count + 1 完成原子增减;同时保留一条离线校准任务,定期以关系表为准修正计数偏差,防止因异常路径导致的长期漂移。
三、接口设计与幂等控制
3.1 接口语义
建议将收藏与取消收藏设计为两个独立接口,而非一个 toggle 接口:
POST /api/favorite // 收藏DELETE /api/favorite // 取消收藏GET /api/favorite/state // 查询状态GET /api/favorite/list // 分页列表
toggle 接口在弱网重试场景下语义不明确——客户端重发可能把“已收藏”又切成“未收藏”。独立接口配合幂等语义更安全:重复收藏返回成功且不改变数据,重复取消同理。
3.2 幂等实现
幂等的核心是“操作结果与执行次数无关”。对收藏而言,利用唯一索引 + INSERT IGNORE 或捕获唯一键冲突即可:
public boolean favorite(long userId, int targetType, long targetId) {try {favoriteMapper.insert(userId, targetType, targetId);countService.increment(targetType, targetId, 1);return true;} catch (DuplicateKeyException e) {// 已存在有效记录,视为成功,不重复计数return true;}}
注意计数增减必须与关系写入保持“同一业务事实”,否则会出现“记录存在但计数为 0”的不一致。常见做法是仅在插入真正生效时递增计数,取消时仅在状态由有效变为无效时递减。这要求存储层能返回“本次操作是否改变了状态”,例如使用带条件的 UPDATE ... WHERE status = 1 并检查影响行数。
3.3 状态查询的批量优化
列表页往往需要为每条内容渲染收藏按钮态,逐条查询状态会产生 N 次请求。应提供批量接口:
POST /api/favorite/state/batchbody: { "targetType": 1, "targetIds": [101, 102, 103] }resp: { "101": true, "102": false, "103": true }
服务端一次 IN 查询即可返回,配合本地缓存可进一步降低存储压力。
四、分页查询的稳定性问题
收藏列表按 created_at 倒序分页时,若用户在浏览过程中新增或取消收藏,传统 LIMIT offset, size 会出现重复或漏项。推荐使用游标分页(keyset pagination):
SELECT target_type, target_id, created_atFROM user_favoriteWHERE user_id = ?AND status = 1AND (created_at, id) < (?, ?)ORDER BY created_at DESC, id DESCLIMIT ?;
以 (created_at, id) 作为复合游标,既保证顺序唯一(同一秒内多条记录用 id 兜底),又避免深分页的偏移扫描。前端只需透传上一页最后一条的游标即可。
若产品强依赖页码跳转,则需接受一定的不精确性,并在接口文档中明确说明;或采用“快照 + 增量”的折中方案,在会话内固定数据集。
五、高并发写入与热点问题
收藏是典型的“写多读也多”场景,热点内容可能在同一时刻被大量用户收藏,计数行成为行锁争抢点。可采取以下分层策略:
- 计数分片:将单个计数拆为 N 个分片行,写入按
hash(userId) % N落片,读取时求和。牺牲少量读性能换取写入吞吐。 - 异步合并:写入侧先落消息队列,由消费者批量合并计数更新,降低数据库压力;代价是计数存在秒级延迟,需在产品上接受“最终一致”。
- 本地聚合:单机内先做短窗口聚合再批量落库,适合突发流量削峰。
选择哪种取决于业务对计数实时性的要求。内容热度展示通常可接受秒级延迟,异步方案性价比最高;若涉及交易或权益,则必须强一致,应优先保证正确性而非吞吐。
六、缓存策略与一致性
收藏状态是典型的“读多写少、按用户维度聚合”的数据,适合多级缓存:
- 本地缓存:缓存用户最近的状态查询结果,设置较短 TTL(如 30 秒),降低远程调用。
- 分布式缓存:以
user_id + target_type + target_id为键缓存布尔状态,写入时删除对应键(Cache Aside)。 - 列表缓存:缓存首页收藏列表,翻页时按游标增量加载,避免整列表失效。
一致性上遵循“先更新数据库,再删除缓存”,并对删除失败做重试补偿。对于计数类数据,可接受短暂不一致,通过前述校准任务兜底。
七、演进路径与工程建议
一个可持续演进的收藏系统通常经历三个阶段:
- 单表阶段:关系表 + 计数表,满足基本功能,重点把幂等与索引做对。
- 读写分离阶段:列表查询走只读副本,写入走主库,缓解读压力。
- 分层存储阶段:热数据(近期收藏)留在行存,冷数据归档到列存或对象存储,列表查询按时间范围路由。
工程上还有几点容易被忽视:
- 内容删除后的悬垂引用:内容下架后,收藏记录仍指向不存在的目标。列表查询需做存在性过滤,或在下架时异步清理并补偿计数。
- 权限与隐私:收藏列表默认仅本人可见,若支持公开需单独授权模型,避免越权读取。
- 可观测性:为收藏、取消、计数变更埋点,监控成功率与耗时;对计数偏差设置告警阈值。
结语
收藏功能的难点不在“存一条记录”,而在幂等、计数一致性、分页稳定性与热点写入这几个交叉点上。把数据模型的唯一约束设计对、把计数与关系写入绑定为同一业务事实、用游标分页替代偏移分页,这三点做到位,后续的缓存与分片才有稳固的地基。建议在首版就补齐幂等与批量状态查询能力,避免上线后因重试与 N+1 查询被迫重构。

登录后可评论,请前往 登录 或 注册