


























内容类app的点赞功能设计
点赞功能即用户对内容点赞,点赞后用户可以看到该内容最新的点赞数。用户可以查询自己的点赞列表,可以查看内容的点赞列表。
level1:用户对内容点赞时,往数据库中插入一条记录。取消点赞时,删除此条记录。
表结构是:id、user_id(用户id)、content_id(内容id)、create_time、update_time
为了防止重复点赞,需要在user_id和content_id上加唯一索引。
上面方案读写全部都走数据库,在并发大的情况下,对数据库会造成很大压力,响应时间也会很慢。
level2:引入redis,把高并发的读写放到redis中。
创建两种类型的key,一种是set类型的键值对,存放点赞事件,key形如content:likes:{user_id},member是content_id。点赞时,就sadd,取消点赞时,就srem。另一种是string类型的键值对,存放内容的点赞量,key形如content:likesnum:{content_id},value是该内容对应的点赞量。点赞时,就incr 1,取消点赞时,就incr -1。
举个例子,如果u1对c1点赞,则先sadd content:likes:u1 c1,如果返回0,则表示重复点赞,直接返回给前端即可。如果返回1,则再incr content:likesnum:c1,给点赞数加1。最后把点赞事件通过消息队列异步存到数据库中,注意要幂等,即insert ignore。这样,在拉取内容列表时,查用户对内容是否点了赞,只需执行sismember,查内容的点赞数,只需执行get。用户查询自己的点赞列表时,就有问题了,set没有存点赞时间,无法按照点赞时间倒序取。所以,要把set类型改为sorted set类型,key形如content:likes:zset:{user_id},member是content_id,score是点赞时间对应的时间戳,这样就会自动按照点赞时间排列。用户查询自己的点赞列表,只需zrangebyscore。用户查看内容的点赞列表,用当前两种key都不行,所以需要再创建一个sorted set类型的键值对,key形如content:likes:zset:{content_id},member是user_id,score是点赞时间对应的时间戳。这样,当执行一次点赞操作,需要操作三个key,那么最好是用lua脚本原子式执行多命令。
level3:高并发下,点赞事件一条条写到数据库,会对数据库造成压力。此外,还必须要考虑热点数据。
一条条写数据库,要改成批量写数据库。需要在应用层积攒消息,即一直poll,直到积攒到一定量或是积攒一定时间,再批量入库,再commitSync。注意,如果是一个消费者消费多个分区,则需要用那个有参数且参数是各分区偏移量的commitSync()方法,以保证每个分区都能commit正确。
热点数据,如一个内容被几十万个用户点赞,或者一个用户点赞了几十万个内容,那么存储点赞事件的那两个key就会变成大key,占用太多内存。我们可以只保留最近1万条记录,之前的记录清掉。这样的话,查询点赞列表时,如果取到1万条之后,就从数据库中取。基本上不会有人会翻到1w条之后。
还有一种热点数据,即一个内容短时间内收到了大量的赞,导致短时间内频繁操作存储点赞量的这个key。如果redis是集群,那么我们可以把这个key分片,即拆分成content:likesnum:{content_id}_0,content:likesnum:{content_id}_1。。。content:likesnum:{content_id}_n,让这些key分到不同的redis节点上,这样就可以提升吞吐率了。
点赞系统的设计核心是:
1. 使用 Redis 承担高并发读写:
- ZSet 存用户/内容点赞关系(支持分页)
- String 存点赞计数
2. 使用 Lua 保证多 key 操作原子性
3. 通过 MQ 异步落库,并采用批量写提升吞吐
4. 针对热点问题:
- 大 key:只保留最近数据 + DB 兜底
- 热点计数:采用分片计数(sharding counter)
5. 分片计数结合 Redis Cluster,实现多节点并行写入,
突破单节点吞吐瓶颈
6. 通过幂等设计(唯一索引)保证数据一致性
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。