场景题:点赞系统设计

刷到此贴的友友春招/暑期必上岸!!!

前言

鼠鼠在秋招的过程中多次被问到场景题,中大厂的考察频率相当之高,一般会放在最后一个问题用来拖时间,也遇到过上来就问你怎么设计一个系统(面试官以此来决定后面对你的态度)。所以鼠鼠准备开这个场景题栏目,分享在秋招过程中遇到的场景题以及如何进行回答,感兴趣和感觉有帮助的友友点个关注和赞吧,你们的点赞和关注是鼠鼠持续更新下去的最大动力!!!

对于场景题,鼠鼠觉得拿到一道题,首先要思考的是业务逻辑,然后就是在这个业务上会有多大的qps请求量,面试官经常会对你设计的方案和系统提出高并发/大流量的情况下会出现什么问题,你如何去解决,从而考察你设计系统的高可用性和系统性。

话不多说开启今天的主题,点赞系统设计吧!!!

大家在刷牛客的时候,刷到优质的帖子会点赞加收藏,认为这个博主有不错的产出还会点一个关注(暗示友友们给我点个免费的关注和赞嘛,球球了)那么点赞其实也就成了牛客的核心功能之一,因为每日用户这么多的情况下,点赞所承受的并发其实是相当高的。而且点赞无论是对贴主还是看客,都是比较重要的,涉及到发帖的质量和自身的曝光度。一个大V的帖子,其点赞量,其实对后台的系统的压力,数据库的压力是很具有考验的。

在这种情况下应该如何设计点赞系统?那么关于系统设计题,我在上篇文章里已经提到了一些通用的方法论,感兴趣的小伙伴可以去看一看,当然我这里也贴心的贴在这里,帮助大家更好的理解。

(1)明晰项目诉求:点赞系统即为存储每一个用户发布的帖子有哪些人点赞了,总的点赞数是多少;每一个用户具体点赞了哪些帖子,赞过的帖子总数是多少(其实这个逻辑友友们点开自己的牛客收藏和点赞其实就一目了然了)整体上是一个多对多的关系。同时关注请求量,是否需要做高并发/高可用的扩展设计;此外对实时性是否也要要求,追求强一致性还是最终一致性

(2)确定业务逻辑:对于每个帖子,需要记录点赞的用户,点赞的总人数,取消点赞时点赞数要相应减1,用户查看自己发布的帖子需要显示总点赞数,以及展示一个点赞用户的列表(简单概括为增删改查)。同理,对于观看帖子的用户,需要记录用户点赞了哪些帖子,用户点赞帖子的总数,在我的 - 点赞页面需要展示出用户点赞过的帖子……

(3)架构设计:存储(MySQL/Redis)+服务(是否需要拆分为多个服务)

友友们会发现,整体的业务逻辑并不是很复杂,但是因为这是面试题,面试官就会问你在高并发的情况下你设计的系统会有什么问题。这其实也是我回答场景题的惯用套路,根据请求量一步一步迭代自己的设计,我们在实际生产过程中也同样是这样一个步骤,先思考最简单的实现,然后一步一步迭代,直到可以抗住高并发,实现高可用(满足面试官的要求

业务逻辑梳理清楚了,接下来我们就来到存储与服务设计了,因为点赞信息是比较重要的,所以这里还是考虑使用MySQL做持久化存储。(做过黑马点评的友友可能直接就会想到Redis做存储,利用incrby 自增命令快速实现点赞功能,这当然是可以的,但是从系统设计和信息丢失的角度来看,先从MySQL入手可以保证数据的安全性,也更能体现面试者的系统设计能力,逐步优化的一个思想)

数据表的设计

点赞记录表

1)type,也就是业务类型,你是为视频点赞还是为评论点赞,还是为其他业务类型点赞

2)作者ID

3)作品ID

4)userID:点赞人ID

5)点赞时间

6)点赞动作(1 like/ 2 unlike)

点赞计数表

1)type,也就是业务类型,你是为视频点赞还是为评论点赞,还是为其他业务类型点赞

2)作者ID

3)作品ID

4)点赞数量

5)取消点赞数量

以上是我的点赞表的设计思路,还有其他的点赞表设计,大家可以根据自己的理解进行修改。 在请求量不大的情况下,可以直接用MySQL承载。后端服务会处理点赞的业务逻辑,然后将点赞记录存储到数据库。

点赞的业务逻辑

1 )前端用户发起点赞请求到达后端服务。后端服务会判断表内是否有之前的点赞记录,因为前端用户可能会重复的去点击点赞按钮。

2.1 )如果没有记录就插入点赞记录,之后点赞计数表加一 2.2 )如果表内有了用户对作品的点赞记录,就去比较点赞的时间

3.1)如果时间小于表内记录的时间,说明当前的请求是之前的请求,丢掉就可以了

3.2)如果是大于表内的记录的点赞时间,就继续判断状态

4.1)如果已经是点赞状态了,说明就是重复的点赞请求,就丢掉就可以了

4.2)如果是取消点赞的状态,就更新记录为点赞状态即可,同时可以增加点赞计数表的数量加一 整个点赞的逻辑就处理完了。

流程图如下:

取消点赞的逻辑

1)当前端用户取消点赞,后端服务检查计数表内是否有对应用户对该作品的记录。

2.1)如果没有,说明之前没有点赞过,直接丢到返回前端即可。 2.2)如果有记录,继续判断时间,如果时间小于数据库内记录时间,说明是之前的请求,直接丢掉就可以了。

3.1)如果是大于表内记录时间,说明就继续判断记录的点赞状态。

3.2)如果表内是取消点赞的状态,说明是重复请求了,直接丢到就可以了。

4)如果表内是点赞状态,更改为取消状态,之后点赞数减一即可。

流程图如下:

并发问题

整个过程是有并发的问题,需要加分布式锁。

点赞过程中的并发问题

1)重复插入问题:在并发情况下,可能会有多个点赞请求同时到达后端,并且都检查到数据库中没有该用户对该作品的点赞记录,然后都执行插入操作,导致重复插入点赞记录。

2)计数不准确问题:当多个点赞请求并发执行时,可能会出现点赞计数表加一的操作不是原子性的情况。例如,两个并发请求同时读取了点赞计数表中的值为 10,然后各自执行加一操作并更新回数据库,最终结果可能是 11 而不是正确的 12。

取消点赞过程中的并发问题

1)重复减一问题:类似于点赞过程中的计数不准确问题,当多个取消点赞请求并发执行时,如果点赞数减一的操作不是原子性的,可能会导致点赞数被多减。例如,当前点赞数为 5,两个并发的取消点赞请求同时读取点赞数并执行减一操作,最终结果可能是 4 而不是正确的 3。

2)状态更新不一致问题:在并发环境下,可能会出现两个请求同时判断点赞状态为点赞,然后一个请求先将其更新为取消状态并减一,另一个请求再更新时可能会出现错误的结果。

可以采用数据库事务来确保操作的原子性和一致性,或者使用分布式锁来保证同一时间只有一个请求能执行点赞或取消点赞的操作。同时,在数据库设计上,可以考虑使用乐观锁或悲观锁机制来防止数据冲突。

查询点赞列表和点赞数

在获取作品点赞数时,通常有两种方式:一种是逐个获取每个作品的点赞数,另一种是通过批量接口一次性获取多个作品的点赞数。

逐个获取作品点赞数:如果有多个作品需要获取点赞数,每次都单独向服务器发送请求获取一个作品的点赞数,那么每一次请求都会产生一次网络 IO。例如,要获取 10 个作品的点赞数,就需要进行 10 次网络请求,这会消耗较多的网络资源和时间。

使用批量接口获取点赞数:批量接口允许在一次请求中传递多个作品的标识(如作品 ID),服务器接收到请求后,会一次性处理这些作品的点赞数查询,并将结果返回。这样,无论要获取多少个作品的点赞数,都只需要一次网络请求。例如,同样是获取 10 个作品的点赞数,通过批量接口,只需一次网络请求就能完成,相比逐个获取,大大减少了网络 IO 的次数,提高了数据获取的效率,通过批量接口依次获取来减少网络IO的次数,节省了网络带宽和请求时间。

引入Redis作为缓存

当流量大起来以后,此时数据库的读写锁的竞争就会加剧。考虑加入缓存层Redis。

既然引入了缓存,那么面试官肯定会问到一个经典的问题,那就是:缓存和数据库的一致性是如何解决的,这里有多种解决方案,但常用的其实也就是两种:先更新数据库,再删除缓存;通过canle订阅binlog日志,再更新缓存,实现最终一致性。

关于缓存和数据库的一致性后面我也会专门出一个专题分享给大家。

引入Redis作为缓存后,所有的写请求还是会先写入到数据库,读请求会优先到达缓存层去检查数据,没有再去数据库内加载数据填充到缓存中。

Redis中的数据存储

Redis会存储两类数据:存储点赞列表和点赞数

1)点赞列表:使用zset结构,key由 userid:type 组成,表示的含义即为某个用户执行点赞/评论的作品

value就是作品ID+时间戳的set的集合,基于zset可以根据时间戳进行排序 注意zset的长度是有限制的,如果无限制的往zset内添加数据就可能会有big key的问题,可能会阻塞Redis的主线程,影响正常的请求。

2)点赞数:使用string结构即可。key是作品ID,value是具体的点赞数

业务逻辑

查询点赞数:先是去Redis查看某个作品的点赞数,如果存在直接返回给客户端,如果不存在则查询数据库计数表内的数据。然后存储到Redis内,最后返回给客户端数量即可。

点赞列表查询:首先还是会去Redis内查询点赞列表,如果不存在,去mysql里查询,将数据写回到Redis的zset内即可。这里依然要注意zset长度,最后会返回给客户端就可以了。如果存在就进行zset分页(类似于MySQL的分页查询,不用直接返回所有的数据,而是根据分数,这里是时间戳,查询所需要的页面数据即可)。如果页面超过了zset分页范围,超出的部分就会去数据库内查询,拼接结果返回客户端即可。如果未超过zset长度限制,直接返回当前页的数据给前端就可以了。

对于写请求,前端用户发起点赞请求,设计到缓存的更新。这里建议采用canal监听mysql的binlog。根据binlog数据变化,更新缓存内的数据。

涉及到两个操作。一个是更新点赞记录,一个是更新点赞数量,由于点赞记录用的是zset处理,并且是有长度限制的。如果超过长度限制,要删除掉多余的元素。整个操作不是原子的,所以用lua脚本来保证整个过程的原子性。

没有采用直接删除缓存类的数据因为点赞是一个很频繁的动作,如果频繁的删除数据,其实会频繁的去数据库加载数据,缓存的命中率其实就会很低,实际上并没有给数据库降低压力。

架构升级

随着业务数据的上升,单机的mysql面临着严重的考验,mysql处理io的能力是有限的,此时考虑到mysql的分库分表来分担单库的磁盘压力,单库的压力。但分库分表其实是会带来系统的复杂性的,还有就是大V数据分布不均匀的问题。当然这里我们只是从系统架构的角度去一步步迭代设计,大家对于分库分表的常见问题也需要有了解,后面我也会出专门的文章来给大家做分享

那么不做分库分表的话,运用三板斧之一的消息队列同样也可以通过异步消息来为数据库来消峰填谷。

那么这里既然使用到了MQ,那么相关的消息不丢失以及消息消费的幂等性问题也就随之而来了,因为本文主题是点赞系统的设计,这些八股问题我们留到后面的文章中再做解释。

当写请求到达时候直接发送到mq,然后返回给前端,点赞成功即可。mq之后就是消费任务,可以订阅点赞的topic进行消费,然后进行点赞的业务逻辑,存入数据库和Redis。

新的架构模式下的业务逻辑

1)一个点赞请求过来的时候,消息方法MQ后可以直接返回客户端点赞成功了,后续由MQ异步的处理写数据库和缓存的操作,这里为了保证消息不丢失可以采用同步发送,将消息持久化到磁盘上。 2)消费任务监听消息的到达进行点赞写入的业务逻辑。这里的点赞还是和前面的初步设计时是一样的,这里不过多赘述了。

3)按照我们之前的架构是先写数据库,然后由canal监听binlog日志的变化,最后更新Redis缓存

总结:

相信大家看完这篇文章对点赞系统这样一个高并发系统的设计已经有了一个初步的印象了。

这里总结概括一下就是:先考虑流量不大的情况,设计出初步的业务逻辑,当流量大起来以后,考虑使用Redis作为缓存,这里考虑缓存和数据库的一致性问题;如果Redis也扛不住了,那就再加上消息队列,进行流量削峰,同时通过异步调用提高客户端的响应速度;最后考虑到数据量庞大的问题,采用分库分表来解决存储和读写效率问题。

大家以后遇到高并发系统就可以围绕这样一个大致思路去作答。这里还有网关/限流等概念没有涉及到,点个关注吧,后续的文章会陆续补充欧,希望可以为你的面试带来帮助!

PS:

其实在整个分析过程中大家可以发现,场景题其实就会把我们背的那些八股和技术运用起来,所以在学习场景题的时候就可以把八股文进行问题,有点像单词背不住就去读阅读文章,在读文章的时候记住八股文,在上面的分析过程中我也有几处进行了随机的八股提问。扫码登录这个过程里MySQL,Redis,分库分表,MQ用的很多,那友友们是不是可以顺带复习一下相关八股呢?

……

好了如果大家有什么问题的话欢迎来评论区交流。包括但不限于文章创作改正意见,后续分享内容(面经,知识输出,经验分享等等),都看到这了,点个免费的关注和赞不过分吧

#大家都开始春招面试了吗##我发现了面试通关密码##暑期实习 ##春招##场景题##八股#

全部评论
最近被场景题问懵了,为什么都要拷打我一个没有任何实习经历的菜b啊
3 回复 分享
发布于 2025-04-01 16:44 上海
写的很好~ 学会了 场景题先设计一个最naive的系统,然后根据面试官的引导,一步步地优化 毕竟好的系统都是迭代产生的 过早的优化是万恶之源
2 回复 分享
发布于 2025-04-24 22:43 北京
mark一下
点赞 回复 分享
发布于 2025-10-31 16:31 河南
mark一下
点赞 回复 分享
发布于 2025-09-13 22:56 广东
按理来说,赞同和取消赞同应该是同一个按钮,赞同的时候,先判断下有无点赞信息,如果没有就加入一条点赞记录,然后更新点赞数量,如果有,那么就对应取消点赞的逻辑,加入一条取消点赞的记录,然后更新点赞数量吧。
点赞 回复 分享
发布于 2025-05-25 18:26 广东
mark一下
点赞 回复 分享
发布于 2025-05-08 21:27 安徽
佬,求更新
点赞 回复 分享
发布于 2025-04-25 00:35 河南
mark一下
点赞 回复 分享
发布于 2025-04-23 12:56 山西
mark
点赞 回复 分享
发布于 2025-04-22 16:58 湖北
mark一下 点赞
点赞 回复 分享
发布于 2025-04-22 11:34 北京
mark一下
点赞 回复 分享
发布于 2025-04-21 09:55 四川
这流程图好眼熟啊,ds 吗
点赞 回复 分享
发布于 2025-04-09 22:25 北京
mark一下
点赞 回复 分享
发布于 2025-04-09 11:32 天津
mark大佬
点赞 回复 分享
发布于 2025-04-01 16:51 湖北
mark一下
点赞 回复 分享
发布于 2025-03-28 19:41 河北

相关推荐

春招的战役还在继续,回望这段时间马不停蹄的面试征程,我前前后后参与了10多家公司的面试,有碰壁后的失落,有意外复活的惊喜,也有拿到offer的踏实,更攒下了不少实打实的求职心得,想和同样在春招里奔波的小伙伴们好好聊聊。先说说最直观的行业感受,这次春招让我真切体会到,Java开发岗位真的是“狼多肉少”,难度和竞争压力远超预期。我重点面了转转,甚至还拿到过一次复活机会,北京、深圳的岗位都逐一尝试,可最终还是北京岗二面折戟,深圳岗一面止步。翻看官网的招聘信息就能发现,大部分技术组每个岗位仅招聘1-2个人,可每周都有大批量的求职者涌入面试,优质hc极度稀缺,想要突围难上加难。也正是因为Java赛道过于拥挤,我及时调整了方向,尝试投递Go语言开发和全栈开发岗位,打开了新的求职窗口,顺利约到了不少优质企业的面试,整体的面试通过率也远比Java岗位高,求职之路瞬间顺畅了很多,这也让我意识到,春招求职适时调整方向、灵活选择赛道,远比死磕一个饱和岗位更明智。付出总算有了回报,目前我的春招战果还算不错,已经拿到了几份offer:竞高文化Java开发岗、好未来golang语言开发岗(口头oc)、微派安卓开发岗,还有淘米游戏golang服务器开发岗等等。作为一名双非院校的求职者,能拿到这些offer,我已经倍感珍惜,也深知这份结果来之不易。其实双非选手在春招里,真的太需要天时地利人和了。学历的短板在大厂面试中很容易被无限放大,有时候明明感觉面试过程发挥得很好,和面试官交流也十分顺畅,可最后还是等来了拒信,这种落差感真的很磨人。那段时间我也陷入过严重的焦虑,刷社交平台总能看到同是双非的大神拿下大厂offer,心里既羡慕又着急,忍不住反复对比,越比越自我怀疑,甚至陷入精神内耗。但慢慢我也想通了,别人能拿到亮眼的offer,背后必然付出了远超常人的努力,这份成果是他们应得的。一味地和他人比较,参考别人的求职经历对标自己,不仅毫无意义,还会徒增烦恼、打乱自己的节奏。与其在焦虑中内耗,不如沉下心来做好自己的事,整理面试复盘、巩固技术知识点、耐心等待下一个机会,这才是最该做的。在这里也真心给正在春招的小伙伴们一个建议:可以多了解行业行情,明确求职趋势,但千万不要盲目和别人比较。每个人的基础、经历、求职节奏都不一样,走好自己的路,放平心态、保持耐心才是最重要的。春招是一场持久战,心态崩了就什么都没了,哪怕暂时没拿到满意的结果,也不要灰心。现在才三月,接下来还有四月、五月的黄金求职期,机会依旧很多。不用着急,不用焦虑,踏踏实实准备,认认真真面试,相信每一个努力的人,最终都能等到属于自己的那份满意offer。
牧羊人_xmy:和兄弟的感悟一样,传统后端Java开发太难了,现在我也在转其他方向
春招至今,你的战绩如何?
点赞 评论 收藏
分享
04-04 21:23
门头沟学院 Java
一直没有勇气书写下书写下这段时间的找实习经历一直在焦虑也一直在碰壁,但是不可否认的是在经历各种碰壁之后自己反而成长的最快首先是一开始的中小型公司,聊的都挺好的,也都愿意发offer一个一个首先介绍吧首先万兴科技这是我觉得非常恶心的一个公司(不刻意抹黑,我只说事实),实际上只有一轮技术面试,后面两轮问的比较少,但是他们是Golang需要转语言,星期三业务部的领导面过之后答应可以入职,跟hr联系之后答应是下周三之前给他们答复,到我周二想详细的了解薪资跟培养制度以及转正,当时hr跟我说的是问一下业务部给我订的职级,hr准备实际的发邮箱offer了,一个小时之后hr跟我说他们业务部早就终止流程,连hr都不知道,属实有点恶心人然后的话就是字节海外电商,一路面到了第四轮技术终面,解释一下为什么有四轮,因为第二轮的算法题面试官让我用最优解写出来,面试官也一直跟我将思路,但是当时没get到核心,没写出来,所以又加了一轮面试,技术终面推迟了两次,每次都是面试前一个小时告诉我面试官临时有事,上来让我用IDE写题,一道字符串操作题,一开始用正则表达式几分钟就写好了,后面又让我自己实现,写完没考虑特殊情况,面试官说知道了直接开始面试,上来就是场景题,一百亿条IP加时间戳的日志数据内存限制100M,统计IP出现次数并且实现排序,我的思路是先对文件进行分片,对日志进行hash取模之后相同IP放到同一个分片文件中,再对小文件内进行IP统计和排序,最后的话就是多路归并排序写入到文件中,后来就是问我高考分数,还有一些八股,其中的话https加密握手聊挺长,我只详细说了RSA机制的握手加密面试官可能不是太熟悉一直问我是这样的吗,一直在追问为什么双方要进行随机数交换还有预主密钥跟他详细解释之后面试官也觉得我说的挺好的,自己就是安全专业的所以对这个使用非对称加密实现对称加密秘钥传输比较熟悉,最后应该是排序挂,那边有更好的人选,第二天又被其他部门捞起来了(贴个手撕:带超时的LRU,最小覆盖子串,下一个排序,最大有效括号长度,版本号比较,编辑距离变种)小米hr联系的时候进行了二十多分钟AI相关知识的问答,联系一面,面试体验比较好,面试官比较实在,直接说八股列表找两个问我,手撕环节是让我搭建一个后端服务简单的进行CRUD可以使用AI,面试时长一个多小时,后面的话面试结果不知道,给Hr打电话也不接应该是KPI腾讯直接没有面试,已经找过校园大使HR帮忙投递但是一直没面,身边的人都给面了就我没有,可能是之前提前批自己的处男面面评不太好,鼠鼠也是无奈啊快手简历挂,米哈游简历挂其他像华为阿里实习开的比较晚,因为自己手上有offer,并且自己还能接受,所以就没面了美团是实习之后面的,一直没时间准备,答的还行,但是因为美团面试平台有问题,没手撕只说了思路题目不难leetcode中的课程表那道题自己没详细整理面经,只是记录每次面试中答的不太好的,所以这里就不贴面经了最后的话选择了一家中小厂吧,还是不透露了呜呜呜,公司发的mac还用的不太习惯,但是AI模型随便用,我一周就用了三十刀,mentor是个大牛也是个老顽童贼有趣,实习体验挺好入职一周多,自己随便写的心得大家不要嫌弃
查看10道真题和解析
点赞 评论 收藏
分享
评论
73
292
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务