点赞系统如何设计?
前言
归功于某马的点赞教学视频,成功把人带偏了,许多人都讲得很烂。
只要是设计,无论怎样我们都要遵守一个原则:不要不设计,也不要过度设计。
只有结合基础 + 扩展 + 实际的场景
才能把这个问题答好。
正文
你可以这样说,
好的,面试官,我觉得无论是什么样的系统设计都要考虑实际的业务情况,因为我看过一些有关软件设计的书籍(自己找一本设计模式的书籍编一下)中就有提到过,不要进行过度的设计,过度的设计所带来的复杂度和维护成本是不一样的。
这边简单说一下我的想法,
点赞的话,按照业务场景可以分为留痕点赞和不留痕点赞:
不留痕点赞
我这里先说一下最简单的不留痕点赞,典型的场景就是直播间的点赞。
直播间的数据不需要记录是谁点赞的,只需要一个单纯的点赞数累加就行了,这个场景的特点就是短时间内点赞数会增长得特别快,而且直播间需要每隔一段时间查询出最新的点赞数,所以这是一个读、写都频繁的场景。
从前端的角度来说的话,肯定是不能让用户的每一次点击行为都去请求一次接口的,因为这样做会对后台造成巨大的压力,前端的话可以将一段时间内用户的点击数收集起来,然后再上报,比如每隔3秒请求一次接口,并且这个接口直接返回当前直播间的点赞数量。
从后端的角度来说的话,对于这种读写频繁的场景,非常适合用 redis 去承接这些流量。我们可以在开播的时候,在 redis 中初始化一个房间,用 string 来存储。前端上报的点赞我们可以调用 redis 的 increase 方法,这个方法是原子加减的,并且调用以后可以返回最新的数量,非常适合这个场景。在关播的时候把这个房间的点赞数据同步到数据库中即可。但是这里面我觉得还有几个注意点,如果单纯的在关播的时候去同步点赞数量,redis出现故障可能会导致数据丢失,所以这里可以起一个定时任务,定时的把直播点赞数据同步到数据库,而且直播点赞这个场景其实数据的准确性也不是特别重要,哪怕数据有一点差异也没关系,所以直播点赞我觉得可以这样子来做。
留痕点赞
还有一种叫留痕点赞,就比较复杂了,它的特点就是我要知道我对哪些对象点过赞,并且还有一些有收藏属性。大概的业务场景可以有几种。比如对一个视频点赞,对一个评论点赞。这里面的核心接口有几个:
- 点赞接口
- 取消点赞
- 批量查询是否对某几个对象是否点赞
- 我的点赞列表
- 某个视频的点赞用户
- 批量查询一些对象的点赞数量
应对这些场景,我们可以基本上确定表结构。这里主要是两张表。一个是用户和对象的点赞关系表,一个是某个对象的点赞数量表。这里面看是否要进行扩展,因为真实的业务场景里会有对视频,对评论的,所以有必要可以加一个type来区分,当然也可以分不同的表来存储。
后台设计的话我也看过很多的文章,很多教程都说要把点赞放到redis,我觉得有一点太绝对了。
这里的设计需要考虑几个维度:
- 数据量
- 并发量
单纯数据库扛
我简单的分析下,如果单纯是数据量不大,并发量不大的情况下,其实只用 MySQL 就可以去解决点赞的问题了。
这6个接口里面其实有并发问题的就是点赞接口和取消点赞接口。
点赞接口主要做2件事,新增点赞关系,变更点赞数量。
新增关系这个可能会产生自增的锁,可以用雪花算法解决,但是变更点赞数量,就像我们上面那个直播点赞一样,会产生行锁。(自增锁、行锁 => MySQL 的相关知识需要去了解一下)
高并发的情况下可能出现排队堆积的情况。但是由于不同的用户同一时间内,只能请求一次点赞接口,所以请求量会比直播间点赞要少很多。
我自己做过测试,拿简单的场景来说,单纯的新增关系,变更数量,jmeter 对 MySQL 进行压测,1000 的并发下,能够达到 1700 多的 TPS,如果我们的请求量在这个以下,MySQL 是完全可以承接的,并且只使用 MySQL 还有几个好处,不用考虑数据库与缓存的数据一致性问题,代码简单,易维护。而且其他接口我也压测过,在 XX 数据下,能都达到 3000+ 的 QPS,所以一般的系统,MySQL 是非常合适的。
使用redis
如果真的并发量很大的话,使用 redis 进行点赞数量的缓存也是一个不错的选择,100w的视频记录,100w个key就行了。如果缓存点赞关系的话,100w个视频产生的点赞接口可能就是上亿级别的了,而且这是不可控的,这种爆发式增长的数据放到 redis 显然不太合适,不过也不是不能放嘛。
而且缓存点赞数量的话,还有一个好处,因为对象多了的话,一般都是要进行分表的,分完表之后,查询就是一个比较大的问题,需要进行跨表查询,所以用 redis 来聚合对象的点赞数量是比较好的选择,redis 同时也支持批量查询一批key的value,很方便实现那些业务。
其他的查询,比如查询是否点赞,点赞用户,点赞列表,这些查询我都是基于点赞关系,都有明确的查询条件,可以通过分表,做主从来解决。
分表可以应对查询耗时长的问题,而主从可以把查询请求分散掉,来应对更多的并发量。
(通过 mysql主从+分表+redis 其实可以解决像知乎啊,CSDN这些场景,基本都问题不大了)
最后的升华
但是其实不管怎么做,MYSQL始终要它的局限性的,比如数据无限增长可能会带来频繁分表的问题,还有数据倾斜的问题,特别是像抖音,快手这种海量点赞的场景。这个我觉得数据库的选型就要重新考虑了。一个是要解决数据快速增长的问题,另一个是点赞,查询的并发量都特别大,甚至可以把点赞关系都可能要放到缓存里。这种场景非常适合分布式数据库,比如 TIDB、MONGODB,这2个库的好处就是可以横向扩展,通过加机器来解决数据增长和查询并发的问题。
我之前看过一篇关于 mongodb 性能测试的文章:
同样的索引,同样的表结构,mongodb在1000并发下的tps可以达到9000,比mysql快4倍。
而且mongodb也有个特点,在数据量不大的情况下,会把数据优先存到缓存里,还会针对数据倾斜自动做数据平衡(原理要了解下),与其用什么 mysql+redis ,其实一个简单的 mongodb集群 就可以解决所有的问题,只要机器堆的足够多,应对抖音这种场景也没什么问题。但是维护mongodb其实比mysql来说相对复杂。这里就看取舍了。
总结
上述的这些东西,需要结合自己的测试和业务场景去说,还有面试官可能发散问的东西,比说什么雪花算法
、自增锁
、行锁
等。
2025/01/01
writeBy kaiven