阅读、点赞、收藏 one

我们项目实现了一个 xxxx

简历 preview

简历上 :

实现了一个通用的 阅读、点赞、收藏 服务。使用 Kafka 进行解耦

数据库表设计 :

通用表设计 :

id,biz,bizid,readCnt,CollectCnt,LikeCnt

为 bizid 和 biz 创建一个联合唯一索引

点赞表设计 :

id,biz,bizid,uid,status

为 bizid 和 biz和uid 创建一个联合唯一索引, 用 status进行软删除

数据库处理

对于 阅读、点赞、收藏 操作都会涉及到一个 check-dothing 的并发问题

即 : 当用户点赞的时候 文章是否已经点赞过了,如果点赞过需要+1,否则创建 ,这里统一采用 do on conflict update 处理

不过需要注意的是 : update 的时候我们需要保证自增 所以需要使用 gorm.Expr 来获取数据库运行的值,而不是直接 select-update

缓存处理

对于写入方,优先写入数据库,后写入redis

对于查询方,优选查redis 然后再查数据库

对于写入的过程,因为我们使用的 redis key 是一次缓存了 3个 like,read,collect 三个字段

所以我们自增的时候使用的是 HINCRBY 指令,对于 key 不存在的情况下会有歧义 。所以使用 lua 控制了一下 仅只有 key 存在的时候才会自增

消息队列

新增 统计事件 引入 kafka 给 阅读、点赞服务解耦

在阅读文章服务,使用 kafka 异步发送,设置 10次每批,1s超时进行聚合发送消息

消费端同样设置 10条每批,1s超时来进行组合消费

每次消费 使用一个 map 进行聚合 同个biz+biz_id的内容,优化之前一次点赞就需要一次 insert 的 操作

并且通过开启事务进行消费,防止 MVCC 中的 刷盘操作,将原先要进行10次的刷盘降低到1次。

问题

一致性问题的处理

不处理

从业务上来说,没有必要要求阅读数、点赞数或者收藏数量一定是严格准确的,因为即便不准确,也不会对用户 产生什么不好的影响。

更进一步说,只有高并发的文章才会有并发问题。而高并发的文章,阅读数、点赞数或者收藏数本身就很多,你 少一点点无所谓。

亮点

大量使用了事务,来保证在同时操作两个表的时候,保持住 ACID 语义。

大量使用了 Upsert 语义 , 因为很多情况下我们都不知道数据库中是否已经存在数据

大量使用 lua 脚本来保证缓存中的数据是正确的, 但是没有彻底解决缓存一致性的问题

在查询接口中,只缓存总数数据,, 并不会缓存个人是否收藏或者点赞的数据。

总分:62 / 100

按「2 年 Go 后端」面试标准看:方向对、有工程意识,但表达粗糙、关键点浅、经不起追问。面试里能撑 15–20 分钟开场,深入一追容易露怯。


分项打分

维度 分数 说明
业务建模与表设计 14/20 通用计数表 + 点赞明细表思路正确,索引方向对
并发与数据正确性 12/20 知道 Upsert / Expr,但 check-then-act、事务边界讲不清
缓存设计 12/20 Cache-Aside + Lua 防空 key 自增有亮点,一致性论证弱
消息队列与解耦 13/20 Kafka 批量、聚合、减刷盘有工程味道
表达与面试叙事 6/10 口语化、错别字多,「大量使用」空洞,像笔记不像口述稿
亮点与深度 5/10 亮点空泛,缺少权衡、失败场景、可观测性

优点

  1. 表设计合理:biz + biz_id 抽象通用互动能力,点赞用 (biz, biz_id, uid) 唯一 + status 软删除,符合常见业务。
  2. 踩过真实坑:ON CONFLICT + gorm.Expr 自增、Redis HINCRBY 空 key 用 Lua 护栏,说明写过代码而不只是背概念。
  3. 有吞吐意识:Kafka 批量生产/消费、按 biz+biz_id 聚合、事务合并刷盘,对 2 年经验来说是加分项。
  4. 一致性取舍有业务理由:承认最终一致、高热内容允许误差,比死磕「强一致」更成熟——前提是能讲清边界。

缺点(面试官会盯的)

  1. 「不处理一致性」太绝对:面试官会问:DB 成功 Redis 失败?Kafka 重复消费?取消点赞与计数对不上?需要有策略(补偿、对账、幂等),不能只说「少一点点无所谓」。
  2. 亮点写法很伤:连续「大量使用事务/Upsert/Lua」像模板,没有场景、没有代价。事务多 ≠ 好,还可能拖慢吞吐。
  3. 技术细节易被打穿:
    • 写库再写缓存:失败怎么回滚/补偿?
    • Lua「仅 key 存在才 incr」:key 不存在时计数会丢吗?靠查询回源还是异步重建?
    • Kafka 聚合:幂等键、乱序、部分失败重试怎么做?
    • 「防止 MVCC 刷盘」表述不严谨,容易被纠正。
  4. 缺个人态查询设计:只说不缓存「是否点赞」,但列表页如何批量查个人态?N+1?位图/本地缓存?没写。
  5. 表达与文档质量差:优选/check-dothing、front matter 的 xxxx、标点混乱,会降低「能讲清楚」的印象。

建议(按面试口述改)

  1. 改成 STAR 口述结构:背景(互动服务压力)→ 方案(表 + Kafka + Redis)→ 难点(空 key、重复消费)→ 结果(QPS/延迟/错误率,哪怕是压测数字)。
  2. 准备 3 个追问标准答:
    • DB/Redis 不一致:异步对账 + 定时校准热点 key
    • Kafka 至少一次:消费幂等(唯一键 + Upsert)
    • 取消点赞:status 翻转 + 计数 GREATEST(cnt-1,0),防负值
  3. 删掉「大量使用」,改成「在点赞写明细+改计数时用事务;计数更新用 Upsert;缓存用 Lua 保证 key 存在才 HINCRBY」。
  4. 补一张口述链路:读请求 → Redis Hash → miss 回源 DB 回填;写请求 → DB Upsert →(可选)发 Kafka → 消费者聚合写计数 → 更新缓存。
  5. 亮点换成可量化的 2–3 条:例如「批量聚合把热点文章写放大降到约 1/N」「Lua 避免幽灵 key」「个人态与总数分离降低缓存体积」。

面试官一句话结论

材料有「做过互动域」的骨架,适合当提纲;按现在写法直接上面试,深度和表达大概卡在 及格偏上。把一致性策略、幂等、失败路径和口述结构补齐,冲到 75–80 比较现实。

使用 Golang 构建