当前位置: 首页 > news >正文

面试官:爆火视频点赞每秒 10w,数据库行锁直接卡死,你怎么破?

最近有个兄弟去面某短视频大厂,前面的架构设计聊得挺嗨,结果面试官最后抛出一个看似简单的“点赞”题,直接把他问到了自闭。

面试官:“现在有个爆火视频,比如春晚刘谦变魔术,一秒钟有 10 万人同时点赞。你代码里写 UPDATE video_statistics SET like_count = like_count + 1 WHERE video_id = 1001;,数据库会发生什么?”

兄弟:“那就加 Redis 缓存啊,异步写库。”

面试官冷笑:如果这 10 万人都点赞同一个视频,Redis 的单 Key 会被打爆(Hot Key)吗?异步写库时,如果 MQ 消费者还是一条条更新,数据库的行锁排队能解决吗?

这兄弟支支吾吾半天没答上来。其实,这道题考的不是“点赞”,而是高并发架构中极其硬核的“写热点(Write Hotspot)”问题。

今天 Fox 带你三步拆解,教你如何用架构师的思维,优雅地消除数据库行锁地雷。

第一招:青铜回答——直接操作数据库(自杀式打法)

很多人的第一反应是:数据库不是有索引吗?直接更新不就行了?

-- 看起来人畜无害的 SQL UPDATE video_statistics SET like_count = like_count + 1 WHERE video_id = 1001;

Fox 深度解析:

这是典型的“行锁地雷”。在 InnoDB 引擎中,更新这一行必须获取排他锁(X 锁)

当 10 万个请求同时涌入时,数据库内部会发生以下惨剧:

  1. 行锁竞争:
  2. 只有一个线程能拿到锁,其余 99,999 个线程全部进入等待队列。
  3. 上下文切换:
  4. 随着队列变长,操作系统的 CPU 资源会全部浪费在线程的唤醒和挂起上。
  5. 死锁检测:
  6. MySQL 内部的死锁检测机制会疯狂扫描这个巨大的等待队列,导致 CPU 瞬时飙升到 100%。

结论:在高并发面前,单行更新就是系统的死穴。此时的吞吐量(TPS)会掉到个位数。

第二招:黄金回答——Redis 计数 + 异步落库

稍微有点经验的会说:不能直接压数据库,得用内存挡一下。

  • 方案:
  • 利用 Redis 的 INCR 命令,点赞先在 Redis 里加 1,然后通过 MQ 异步同步回 MySQL。
  • 瓶颈:
  • 如果是超级热点(比如热搜第一),10w QPS 全部压在 Redis 的一个 Key 上,单机 Redis 吞吐量也会到极限。而且,如果 MQ 消费端依然是“拿一条消息、调一次 +1 库操作”,数据库的行锁压力依然没有任何缓解,只是把压力从前端推迟到了后端。

第三招:王者回答——多级分层聚合 + 批量写合并

真正的工业级方案,核心思想只有四个字:分而治之

1. 接入层本地缓冲(Local Coalescing)

不要让每一个点赞都变成一次网络请求!

  • 策略:
  • 在 Web Server 的内存里,利用 AtomicLong 开辟一个 200ms 的时间窗口。
  • 效果:
  • 这 200ms 内的 500 次点赞在内存里直接合并成一个“点赞 +500”的请求,再发给后端服务。

2. Redis 热点 Key 拆分(Sharding)

模仿 Java 中 LongAdder 的设计思想。

  • 策略:
  • 将 video_id_9527 拆分成多个子 Key:count:9527:1、count:9527:2 ... count:9527:10。
  • 分流:
  • 请求按用户 ID 取模,分散到不同的 Key 和不同的 Redis 节点上。
  • 查询:
  • 查总数时,把所有子 Key 的值求和即可:

3. 消费端批量写合并(Batch Update)—— 绝杀行锁

这是解决数据库行锁的终极手段。

  • 操作:
  • MQ 消费者开启批量消费(一次取 500 条消息),在内存里按 video_id 进行聚合。
  • 执行:
  • 最终在数据库里只执行一条合并后的 SQL:
-- 将 500 次锁竞争转化为 1 次 SQL 执行 UPDATE video_statistics SET like_count = like_count + 500 WHERE video_id = 1001;

由于加锁频率降低了三个数量级,数据库的行锁竞争瞬间消失,吞吐量原地起飞。

面试满分模板(直接背诵)

面试官:如何设计一个支撑 10w QPS 点赞的热点系统?

你:“我会采取‘层层聚合,异步合并’的架构:

  1. 端侧聚合:
  2. 利用本地内存做微小的窗口聚合(如 100ms),将高频散点请求转化为低频批量请求,减少 RPC 调用。
  3. 缓存分片:
  4. 针对热点 Key,采用Redis 计数器分片方案,将写压力分摊到多个分片,规避单点 Hot Key 问题。
  5. 批量写合并:
  6. 这是核心。通过 MQ 异步削峰,并在消费端实现批量聚合。将同一行数据的多次更新在内存中累加,最终以一次 UPDATE ... SET count = count + N 的形式更新数据库。
  7. 容灾兜底:
  8. 配合定期快照和 Binlog 日志,确保在追求极致性能的同时,满足最终一致性。”

写在最后

架构师的价值,就在于面对“物理限制”(如行锁、IOPS)时,能够利用内存和异步化手段把矛盾转移。

http://www.cnnetsun.cn/news/1952932.html

相关文章:

  • 新鲜出炉!Claude Code之父亲授 Opus 4.7 最佳实践
  • 紧急!AI生成代码正悄然腐蚀技术债基线:即刻启用这8项质量守卫协议,避免Q3发布事故
  • GM8775C MIPI转LVDS实战避坑指南
  • 10.机器学习——马尔科夫模型实战:从天气预测到股市分析
  • golang如何实现任务灰度发布执行_golang任务灰度发布执行实现要点
  • 别让虚拟皮肤掏空你的真实生活——写给每一位大学生的消费觉醒书
  • 漫画迷的离线宝库:一键构建你的私人漫画图书馆
  • WebForms SortedList
  • Boost电路PI参数调不好?试试这份基于频域分析的MATLAB调试指南与避坑清单
  • SQL Auto Increment:深入解析自动增长字段
  • 从工程任务调度到代码实现:拓扑排序在ALGraph中的5个关键步骤
  • 生成对抗网络(GAN)通俗解析:AI如何学会“无中生有”?
  • 自回归语言模型(CLM)与大语言模型(LLM):架构差异与应用场景解析
  • 2025最权威的十大AI科研神器推荐
  • 一文读懂VMP、Java2C:APP核心代码是如何被“藏”起来的?
  • ESXi 虚拟机厚置备转薄置备完整教程 | 官方命令一键操作,新手零踩坑
  • 避坑指南:瑞萨e2studio中DTC地址绑定的那些坑——以RA2E1内存操作为例
  • 行波管从原理到设计,0 基础入门全攻略
  • 【嵌入式电机控制#20】BLDC六步换相:从霍尔传感器到精准相位同步
  • 别再手动写JCo3.0连接代码了!用Spring Boot整合SAP RFC接口的完整配置流程
  • Fast Fourier Convolution: Revolutionizing Image Inpainting with Global Context
  • 告别官方教程的坑:Luckfox Pico驱动ST7789V2屏幕的完整避坑与性能调优指南
  • 跨越数据洪流:异步FIFO芯片IDT7204/7205在高速数据缓冲中的实战解析
  • Kubernetes 调度原理剖析
  • Hi3516DV300芯片温度监控实战:手把手教你从寄存器操作到应用层API封装
  • 大模型赛道香?转行工程师掏心窝子告诉你4个血泪真相!避坑指南
  • OpenAI Codex大升级!AI编程还能这么卷?
  • 技术自嗨陷阱:在亚马逊,为何“内部技术叙事”是“外部购买理由”的天敌
  • 如何在Dev-C++中创建新项目并设置选项
  • 智能代码生成不是锦上添花,而是流水线生死线:4个真实产线事故复盘(含SLO暴跌至58%的根因图谱)