点赞业务设计

点赞

内容点赞是一个非常高频的业务场景,功能本身复杂度不高,但是业务场景多,QPS 高,而且由于社区的用户体量,整体点赞的数据量非常大

点赞服务需要提供的接口有

  • 对某个内容点赞(取消点赞)、点踩(取消点踩)
  • 查询是否对 单个 或者 一批内容 点过赞(踩) - 即点赞状态查询
  • 查询某个内容的点赞数
  • 查询某个用户的点赞列表
  • 查询某个内容的点赞人列表
  • 查询用户收到的总点赞数

其中最核心的主要是

  • 用户是否点赞内容
  • 内容点赞数

在刷 Feed 流时,每一次下滑,都需要对数十篇内容进行登录用户是否点赞的判断

方案1

用 Redis 的 set 结构进行存储用户 id

contentId: [userId1, userId2, ...]

用 set 的 Sismember 判断用户是否点赞,用 Scard 判断点赞数量

问题:

  • 如果是热点数据,点赞用户很多,容易出现大 Key 问题
  • 缓存重建的时候需要查询全部点赞用户,容易出现慢查询问题

大 key 的危害

  • 对大 key 执行读请求,会使Redis 实例的带宽使用率被占满,导致自身服务变慢
  • 对大 key 进行删除操作,易造成主库长时间阻塞

方案 2

对大 Key 进行分片处理,对同一动态下的点赞用户,进行分片再放到缓存里,每次操作缓存时先根据 userId 计算分片值

1
2
3
contentId-1: [userId1, userId11...]  // 把用户结尾为 1 的全放到这里
contentId-2: [userId2, userId22...]
contentId-3: [userId3, userId33...]

问题

  • 缓存分片仍然维护了被浏览动态下全部点赞数据,Feed 流场景下,用户浏览过的动态,几乎不会再次被浏览
  • 一些点赞量多的历史动态,有人访问时会重建缓存,重建成本高,但是用率不高
  • 分片增加了业务复杂性,增加了维护缓存的难度
  • 分片会导致产生大量的 Key,Key 也会占用内存空间

方案 3

使用 Hash 结构,userId 作为 key

1
2
3
4
5
6
7
8
9
{
"userId": {
"ttl": 1653532653,
"contentId1": 1, //用户近一段时间点赞过的动态id
"contentId2": 1, //用户近一段时间点赞过的动态id
"contentIdn": 1, //用户近一段时间点赞过的动态id
"minContentId": 3540575, //缓存中最小的动态id,用以区分冷热,
}
}
  • 之前的方案访问老旧内容会重建缓存,性价比低,而且内容下点赞用户并不是一直活跃或重新访问的,所以先方案要区分冷热数据
  • contentId 内容 id,是递增的,一定程度上反应了数据的冷热
  • 缓存中只维护一定时间和一定数量的 contentId ,并增加 minContentId 字段用于区分冷热数据
  • 对于冷数据,直接访问 DB,不再重建缓存

点赞业务设计
http://showyoubug.cn/2024/07/03/点赞业务设计/
作者
Dong Su
发布于
2024年7月3日
许可协议