资讯动态

吊打面试官!从100赞到300万并发,视频点赞高并发解决方案全拆解

发布时间:2026/8/20 7:57:29 来源:尧图企业网站定制
前言面试中“视频点赞功能如何实现”是后端高频面试题看似简单实则暗藏并发陷阱。本文以真实面试对话形式从低并发到百万级高并发逐步深挖从基础实现到大厂方案最后反向碾压面试官同时补充同类高并发场景重点新增“舆情爆发疯狂取消点赞”的处理方案供大家交流探讨。适合后端、中间件方向求职者也适合想夯实高并发基础的开发同学。场景后端开发面试面试官资深后端vs 求职者我看似普通实则暗藏干货第一阶段低并发场景100-1000赞普通小项目面试官小伙子先问个基础的视频点赞数怎么记录写一段简易代码思路。我核心是“防重复存计数”最简方案用两张表配合伪代码直接能写// 1. 数据库设计MySQL // 视频表video(id, like_count) 存点赞总数方便快速查询显示 // 点赞记录表like_record(user_id, video_id) 存用户行为防重复点赞 // 2. 核心接口逻辑极简 public String like(Long videoId, Long userId) { // 判断是否已点赞 if (likeRecordDao.existsByUserIdAndVideoId(userId, videoId)) { return 已点赞不可重复操作; } // 新增点赞记录 likeRecordDao.insert(new LikeRecord(userId, videoId)); // 点赞数1 videoDao.incrementLikeCount(videoId); // 返回最新点赞数 return 点赞成功当前点赞数 videoDao.getLikeCount(videoId); } // 取消点赞逻辑类似删除记录点赞数-1面试官嗯基础思路没问题能解决普通小项目的需求。但如果用户量上来了比如同时有1万个人点赞这个方案会有什么问题我主要两个问题1. 每次点赞都操作MySQLMySQL行锁竞争激烈会出现超时、阻塞2. 频繁读写数据库性能瓶颈明显1万并发大概率扛不住。面试官那怎么优化另外补充一个场景假如这个视频出现舆情很多人疯狂取消点赞这个方案能应对吗我这个问题问得好取消点赞和点赞的核心逻辑一致但低并发场景下取消点赞的核心风险是“计数超减”比如用户没点赞却取消导致点赞数变负。低并发下的取消点赞解决方案直接补充到刚才的代码里核心是“先判断再操作”避免超减// 取消点赞接口低并发版 public String unlike(Long videoId, Long userId) { // 先判断是否已点赞未点赞则直接返回避免超减 if (!likeRecordDao.existsByUserIdAndVideoId(userId, videoId)) { return 未点赞无法取消; } // 删除点赞记录 likeRecordDao.deleteByUserIdAndVideoId(userId, videoId); // 点赞数-1此处需注意避免计数为负可加一层判断 Video video videoDao.selectById(videoId); if (video.getLikeCount() 0) { videoDao.decrementLikeCount(videoId); } return 取消点赞成功当前点赞数 videoDao.getLikeCount(videoId); }至于优化方案引入Redis做缓存把点赞计数和用户去重都放到RedisMySQL只做最终持久化。Redis的INCR/DECR是原子操作能解决并发计数问题Set结构能快速判断是否重复点赞同时也能避免取消点赞时的超减问题性能比MySQL高100倍以上。第二阶段中并发场景1万-10万赞中小平台热门视频面试官不错知道用Redis。那我再问你10万并发点赞用Redis怎么实现要注意什么另外要是舆情爆发1万并发取消点赞Redis方案会出问题吗我核心是“Redis抗并发异步落库”不仅能应对高并发点赞也能完美处理并发取消点赞重点解决“原子性”和“数据一致性”问题极简代码面试直接写// 注入RedisTemplate Autowired private StringRedisTemplate redisTemplate; // 点赞接口 public String like(Long videoId, Long userId) { String likeSetKey like:set: videoId; // 存点赞用户防重复 String likeCountKey like:count: videoId; // 存点赞总数 // 1. 原子判断是否已点赞避免并发重复 if (Boolean.TRUE.equals(redisTemplate.opsForSet().isMember(likeSetKey, userId.toString()))) { return 已点赞; } // 2. 原子记录用户计数Redis原子操作无并发问题 redisTemplate.opsForSet().add(likeSetKey, userId.toString()); Long newCount redisTemplate.opsForValue().increment(likeCountKey); // 3. 异步刷回MySQL避免阻塞接口保证最终一致 CompletableFuture.runAsync(() - { likeRecordDao.insert(new LikeRecord(userId, videoId)); videoDao.updateLikeCount(videoId, newCount); }); return 点赞成功当前点赞数 newCount; }// 取消点赞接口中并发版应对舆情爆发的并发取消 public String unlike(Long videoId, Long userId) { String likeSetKey like:set: videoId; String likeCountKey like:count: videoId; // 1. 原子判断是否已点赞未点赞则返回避免超减核心 if (!Boolean.TRUE.equals(redisTemplate.opsForSet().isMember(likeSetKey, userId.toString()))) { return 未点赞无法取消; } // 2. 原子删除用户计数-1Redis原子操作避免并发超减 redisTemplate.opsForSet().remove(likeSetKey, userId.toString()); // 防止计数为负原子判断后再减Redis脚本保证原子性 String script if redis.call(get, KEYS[1]) 0 then return redis.call(decr, KEYS[1]) else return 0 end; Long newCount (Long) redisTemplate.execute(new DefaultRedisScript(script, Long.class), Collections.singletonList(likeCountKey)); // 3. 异步同步到MySQL删除点赞记录更新计数 CompletableFuture.runAsync(() - { likeRecordDao.deleteByUserIdAndVideoId(userId, videoId); videoDao.updateLikeCount(videoId, newCount); }); return 取消点赞成功当前点赞数 newCount; }面试官异步落库如果失败了怎么办数据不一致怎么解决还有并发取消点赞时Redis的脚本能保证原子性吗我两个兜底方案1. 异步任务加重试机制失败后重试3次仍失败记录日志人工兜底2. 定时任务比如每分钟批量将Redis中的计数同步到MySQL覆盖可能不一致的数据保证最终一致。毕竟点赞场景用户不在乎毫秒级一致最终一致完全满足需求。至于Redis脚本完全能保证原子性——Redis是单线程模型脚本会被当作一个整体执行不会被其他请求打断所以能避免“判断计数0”和“decr”之间的并发问题杜绝点赞数为负的情况刚好应对舆情爆发时的并发取消点赞。面试官可以思路很完整。那如果再极端一点视频突然火了300万用户同时点赞单台Redis能扛住吗要是舆情反转300万用户同时取消点赞又该怎么处理第三阶段高并发场景300万同时点赞/取消点赞大厂级热点视频我直接说结论单台Redis扛不住但标准架构下不管是300万同时点赞还是舆情爆发后的300万同时取消点赞都能完美扛住。面试官为什么单台扛不住怎么解决取消点赞和点赞的处理有区别吗追问考察高并发深度我首先说原因Redis是单线程模型极限QPS大概10万-15万300万并发相当于20倍超出极限直接会导致阻塞、超时甚至Redis雪崩。点赞和取消点赞的核心处理逻辑一致区别在于“计数增减”和“用户记录增删”高并发下两者的优化方案可以复用重点解决“单Key热点”“并发超减”“数据一致性”三个问题解决思路分4步层层递进也是大厂真实方案1. 本地缓存聚合削峰第一关用Caffeine或Guava做本地缓存每台应用服务器先在内存中累加点赞/取消点赞数攒够100次请求或定时1秒再批量写Redis。这样能把Redis的IO压力降低到原来的1/100300万并发直接变成3万并发不管是点赞还是取消点赞压力都能大减。2. Redis集群分片分摊压力按videoId哈希分片多台Redis节点分摊压力。比如用10台Redis300万并发平均到每台就是30万单台Redis完全能扛住避免单点瓶颈。同时点赞和取消点赞的KeySet和计数Key会落在同一台Redis节点保证操作的原子性。3. 热点分桶解决单Key热点把一个视频的点赞数拆成N个小计数器比如10个请求随机写一个桶读总数时把所有桶的计数相加。比如videoId123拆成like:video:123:bucket_0到bucket_9300万并发就变成每个桶30万彻底解决单Key热点问题取消点赞时同样操作对应的桶同时用Redis脚本保证每个桶的计数不小于0。4. MQ削峰终极兜底所有点赞/取消点赞请求先扔到Kafka/RocketMQ消费者异步消费、更新Redis和MySQL。前端只做“操作成功”提示不实时等待计数返回即使瞬间爆发300万并发取消点赞也能通过MQ缓冲不会压垮后端服务。补充代码片段体现分桶思想同时支持点赞和取消点赞应对舆情爆发// 热点分桶实现点赞 public String hotVideoLike(Long videoId, Long userId) { String likeSetKey like:set: videoId; // 1. 去重还是用Set避免重复 if (Boolean.TRUE.equals(redisTemplate.opsForSet().isMember(likeSetKey, userId.toString()))) { return 已点赞; } redisTemplate.opsForSet().add(likeSetKey, userId.toString()); // 2. 分桶随机选一个桶0-9 int bucket new Random().nextInt(10); String bucketKey like:video: videoId :bucket_ bucket; // 3. 桶计数1原子操作 redisTemplate.opsForValue().increment(bucketKey); // 4. 异步合并计数落库定时合并到主计数器同步到MySQL asyncMergeLikeCount(videoId); return 点赞成功; }// 热点分桶实现取消点赞应对舆情爆发的并发取消 public String hotVideoUnlike(Long videoId, Long userId) { String likeSetKey like:set: videoId; // 1. 原子判断是否已点赞未点赞则返回避免超减 if (!Boolean.TRUE.equals(redisTemplate.opsForSet().isMember(likeSetKey, userId.toString()))) { return 未点赞无法取消; } // 2. 原子删除用户记录 redisTemplate.opsForSet().remove(likeSetKey, userId.toString()); // 3. 分桶随机选一个桶与点赞时的桶无关保证分散压力 int bucket new Random().nextInt(10); String bucketKey like:video: videoId :bucket_ bucket; // 4. 桶计数-1原子判断避免超减Redis脚本 String script if redis.call(get, KEYS[1]) 0 then return redis.call(decr, KEYS[1]) else return 0 end; redisTemplate.execute(new DefaultRedisScript(script, Long.class), Collections.singletonList(bucketKey)); // 5. 异步合并计数落库删除MySQL中的点赞记录 asyncMergeLikeCount(videoId, userId, true); return 取消点赞成功; }// 合并分桶计数同步到主计数器和MySQL支持点赞/取消点赞同步 private void asyncMergeLikeCount(Long videoId, Long userId, boolean isUnlike) { CompletableFuture.runAsync(() - { Long total 0L; // 累加10个桶的计数 for (int i 0; i 10; i) { String bucketKey like:video: videoId :bucket_ i; total redisTemplate.opsForValue().get(bucketKey) null ? 0 : Long.parseLong(redisTemplate.opsForValue().get(bucketKey)); } // 同步到主计数器和MySQL redisTemplate.opsForValue().set(like:count: videoId, total.toString()); videoDao.updateLikeCount(videoId, total); // 取消点赞时删除MySQL中的点赞记录 if (isUnlike userId ! null) { likeRecordDao.deleteByUserIdAndVideoId(userId, videoId); } }); } // 重载方法点赞时调用 private void asyncMergeLikeCount(Long videoId) { asyncMergeLikeCount(videoId, null, false); }面试官那Redis挂了怎么办数据会不会丢尤其是并发取消点赞时Redis宕机取消操作会不会丢失我这个问题其实可以拆成两个点也是高并发设计的核心兜底逻辑同时兼顾点赞和取消点赞1. 数据不丢不管是点赞还是取消点赞请求先写Redis同时异步发MQ消费者从MQ消费并落库MySQL。即使Redis宕机重启也能从MySQL中读取历史点赞数和点赞记录重新加载到Redis点赞记录加载到Set计数加载到分桶数据不会丢。而且MQ有持久化机制即使MQ也挂了消息会存在磁盘上重启后能继续消费避免取消操作丢失。2. 服务不崩Redis挂了之后前端做友好降级——点赞/取消点赞按钮置灰提示“操作太火爆请稍后再试”不让流量直接打崩MySQL。同时启动Redis哨兵模式或集群主从切换快速恢复Redis服务期间MQ继续缓冲点赞和取消点赞请求恢复后批量消费不影响用户体验。另外还要做幂等性处理以userIdvideoId为唯一键不管是Redis操作还是MySQL落库重复的点赞/取消点赞请求都不会重复计数/删除避免超赞、少赞也避免重复取消导致的计数异常。第四阶段反转吊打面试官补充面试官没考虑到的点含舆情取消场景我面试官其实还有两个点可能你没考虑到也是实际生产中容易踩坑的地方尤其是舆情爆发、大量取消点赞的场景我补充一下1. 点赞记录的存储优化如果视频点赞数达到千万级点赞记录表会变得非常大查询和插入性能都会下降取消点赞时删除记录也会变慢。这时候可以用Redis的Bitmap结构存储用户点赞状态比Set更节省内存比如1000万用户只需要1.2MB内存而且判断是否点赞、取消点赞修改Bitmap位的速度更快尤其适合大量并发取消的场景。2. 冷热数据分离大部分视频是冷门视频点赞数很少没必要一直放在Redis中占用内存。可以做冷热分离——热门视频点赞数1万放在Redis冷门视频直接操作MySQL定期清理Redis中的冷门数据提高Redis内存利用率。同时舆情爆发时热门视频会瞬间变成“取消热点”冷热分离能避免冷门视频占用Redis资源保证热点视频的取消操作流畅。3. 舆情应急方案如果视频舆情爆发出现百万级并发取消点赞除了上述方案还可以临时开启“计数降级”——暂时关闭Redis到MySQL的实时同步只保留Redis计数待舆情平息后再批量同步到MySQL减少MySQL压力同时前端可以做“延迟显示”取消点赞后前端先显示“取消成功”后台异步更新计数避免用户等待。还有刚才说的分桶方案其实可以再优化按用户ID哈希分桶而不是随机分桶这样能避免同一用户的点赞请求分散到不同桶减少合并计数时的计算压力同时也能避免极端情况下某个桶的压力过大尤其适合大量用户同时取消点赞的场景。面试官愣了一下点头不错不错这些细节确实是生产中容易忽略的尤其是舆情爆发后的取消点赞场景你考虑得非常全面比我预想的还周到。我其实还有一个点高并发下前端也需要做优化——比如点赞/取消点赞按钮防重复点击前端防抖节流避免用户快速点击导致的重复请求从源头减少并发压力同时舆情爆发时前端可以限制用户“短时间内多次取消/点赞”防止恶意刷操作。毕竟高并发优化是全链路的不是只靠后端。面试官微笑很好超出我的预期了这个岗位你稳了。延伸讨论同类高并发场景含取消/撤回操作欢迎评论区交流其实视频点赞的高并发解决方案本质是“缓存抗并发异步落库热点拆分全链路优化”这套思路可以直接复用在以下同类场景中尤其适合“操作撤回”类似点赞取消点赞的场景大家可以评论区讨论各自的实现方案和踩坑经历1. 文章点赞、评论点赞和视频点赞逻辑完全一致核心是“用户去重计数抗并发”取消点赞的处理也完全复用唯一区别是文章/评论的热点分散单Key压力比热门视频小。2. 商品收藏/取消收藏和点赞逻辑类似但收藏需要持久化更严格用户取消收藏后再次收藏要能正常显示可以在Redis和MySQL之间加一层缓存一致性校验避免数据偏差同时取消收藏的并发处理和取消点赞一致重点防止“重复取消”导致的异常。3. 直播间礼物计数比点赞更复杂需要实时显示礼物总数、单个用户送礼次数同时支持“礼物撤回”类似取消点赞可以用“本地缓存Redis分桶MQ削峰”同时需要做实时计数推送用WebSocket撤回时重点保证计数不超减、礼物记录可追溯。4. 投票功能比如选秀投票、活动投票和点赞类似但可能有投票次数限制比如每人每天3票支持“取消投票”撤回需要额外加次数限制的缓存比如用Redis的Hash存储用户每日投票次数同时要防止刷票结合IP限制、用户行为校验取消投票时需恢复用户投票次数。5. 计数器类场景比如文章阅读量、视频播放量不需要用户去重核心是抗并发计数可用Redis INCR异步落库热点分桶拆分单Key压力这类场景一般不需要“撤回”比如阅读量不能减少但如果有特殊需求比如误统计需扣除可复用取消点赞的“原子减防超减”逻辑。总结视频点赞看似简单实则是高并发设计的缩影——从低并发的MySQL直接操作到中并发的Redis缓存异步落库再到高并发的本地缓存Redis集群分桶MQ削峰层层递进核心都是“削峰填谷、分散压力、保证最终一致”。尤其需要注意舆情爆发后的“疯狂取消点赞”场景其核心难点在于“并发超减”“数据一致性”和“应急兜底”通过Redis原子操作、脚本保证、MQ缓冲和应急降级就能完美应对。面试中面试官考察的不仅是基础实现更是高并发场景的兜底思维、细节优化和全链路意识而“取消点赞”这类反向操作往往是面试官判断候选人能力的加分项。掌握这套思路不仅能轻松应对面试更能在实际工作中解决各类高并发计数及反向操作问题。最后欢迎大家在评论区讨论你在实际开发中遇到过哪些点赞/计数相关的高并发问题尤其是舆情爆发后的取消操作你是怎么解决的还有哪些同类场景可以复用这套方案

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价