【简历进阶篇】大厂高并发利器:Single-flight机制深度解析

📅 2026/7/6 3:06:43
【简历进阶篇】大厂高并发利器:Single-flight机制深度解析
个人主页代码不加冰欢迎来访作者简介java后端学习者❄️个人专栏LeetCode刷题日记 苍穹外卖日记SSM框架深入JavaWeb✨命运的结局尽可永在不屈的挑战却不可须臾或缺前言大家好我是代码不加冰继学完黑马点评之后我们算是正式入门了后端但是如果仅仅是这两个项目那是完全不够看的因此我这个暑假准备学两个新的项目写在简历里然后再搞一下开源为简历做准备算是同步进行算法和八股也不能落下。因此在这里开设一个新的专栏来分享一些新项目的重要技术以及我自己的一些收获也可以从中学到怎么入手一个新的项目。摘要这里给大家分享一个技术也不能说是技术算是一种模式在此之前我都没听过不过学完之后感觉还是很有用的一章肯定是讲解不完的欢迎大家持续关注。在暑期简历备战之际本文为你解锁一个大厂高并发架构中的核心模式——Single-flight单飞机制。当热点数据导致缓存击穿时Single-flight 能通过请求分组与状态跟踪让成百上千个并发线程只由“第一个幸运儿”去冲击底层资源其余线程原地阻塞等待并直接“共享结果”将 DB 压力骤降至O(1)。文章不仅深度剖析了其核心原理与应用场景还直击痛点揭示了单 JVM 环境下线程枯竭、OOM 以及异常共享等核心缺陷与补救措施。最后结合分布式架构演进类比分布式锁引入基于 CAP 理论的“全局锁、状态机、心跳续期及 L1 降级”等分布式单飞核心设计助你全方位攻克高并发流量穿透难题打造差异化简历亮点Single-flight单飞机制是一种并发控制模式它的核心思想是对于同一个资源的多个并发请求只允许第一个请求真正执行其他请求等待第一个请求完成然后共享同一个结果。它的目的就是避免重复计算、重复访问数据库或远程服务防止瞬时高并发把后端打爆。举个例子假设你的系统有一个接口GET /user/1001正常情况下1000 个用户同时访问 │ ▼ 1000 次查询数据库数据库SELECT * FROM user WHERE id 1001如果数据库一次查询需要100ms那么1000个相同的SQL实际上完全是重复劳动。使用 Single-flight 后流程变成1000 个请求 │ ▼ 发现 key user:1001 已有人在查询 │ ├──────────────┐ │ │ 第一个请求 后面999个请求等待 │ │ ▼ │ 查询数据库 │ │ │ ▼ │ 得到结果 │ │ │ 通知所有等待者 ◄──────┘ │ ▼ 1000 个请求一起返回数据库只查询1次为什么叫 Single-flight这个名字来自 Go singleflight package。意思类似同一趟航班Flight只飞一次。例如很多人都想去北京 → 上海没有 Single-flight1000 架飞机有 Single-flight1 架飞机 1000 人一起坐请求也是一样同一个 key user:1001 大家共享一次执行结果Single-flight解决什么问题主要解决① 缓存击穿最经典例如Redisuser:1001突然过期瞬间5000 个请求全部发现Redis 没有于是5000 次数据库查询数据库直接压力暴增加入 Single-flightRedis miss ↓ 只有一个请求查数据库 ↓ 其他4999个等待 ↓ 数据库返回 ↓ 全部共享结果数据库压力从5000 次 ↓ 1 次② 防止重复调用远程接口例如调用 AIGPT或者支付查询订单查询如果100 个线程都请求order123其实只需要查一次即可。③ 防止重复计算例如生成 PDF生成一次需要10秒如果100个请求同时生成100次浪费 CPU。Single-flight只生成一次 其他请求等待 共享生成结果核心原理Single-flight 的原理非常简单直观请求分组使用一个唯一的 key 来标识相同的请求如缓存 key、接口参数哈希状态跟踪内部维护一个 mapkey 是请求标识value 是正在执行的请求状态结果共享当新请求到来时先检查 map 中是否已有相同 key 的请求在执行如果有直接等待该请求的结果如果没有创建一个新的请求并执行同时将其加入 map结果广播当请求执行完成后将结果返回给所有等待的请求并从 map 中移除该请求可以用一个生活中的例子来理解多个人同时想点同一家外卖与其每个人都打开 APP 下单不如大家凑在一起由一个人下单然后大家共享这份外卖。这就是 Single-flight 的工作方式。单 JVM 环境下的核心缺陷与隐患虽然单 JVM 避免了分布式网络 I/O但由于所有线程都在同一个内存堆Heap中它的缺陷会直接反映在 线程模型 和 内存管理 上1. 线程大面积阻塞与线程池耗尽Thread Starvation隐患在单 JVM 中Tomcat/Jetty 等 Web 服务器的业务线程池如 200 个线程是有限的。后果如果底层数据库DB变慢执行单飞任务的那个线程迟迟不返回导致后续所有请求该 Key 的 JVM 业务线程全部进入WAITING或TIMED_WAITING状态。如果热点 Key 较多整个 JVM 的线程池会在瞬间被榨干导致整个服务无法响应其他任何请求。后果还是很严重的。2. 内存突增风险OOM 隐患隐患虽然多个线程共享了一个结果对象但在等待期间每个停留在future.get()或锁等待处的线程都会占用一定的JVM 栈内存Thread Stack通常每个线程默认 1MB。后果高并发下大量线程积压会导致 JVM 线程栈内存开销激增。如果单飞返回的数据量非常大例如一个巨大的 JSON 字符串或大列表虽然对象只有一份但如果引用的上下文中处理不当可能会导致对象在堆中存活时间过长频繁引发小范围的 GC 抖动。3. 异常共享一错皆错与缓存空穿透隐患如果那个幸运的 JVM 线程在执行loader.get()时遭遇了临时的数据库连接超时抛出了RuntimeException。后果单 JVM 内所有等待这个Future的线程都会同时收到ExecutionException。这种“错误共享”会导致本该只有 1 个用户报错的偶发问题瞬间扩散到所有并发访问的用户。4. 无法应对集群扩展隐患单 JVM 的单飞机制只在当前进程内有效。后果如果你的服务未来为了抗流从单 JVM 扩展到了 10 台机器的集群。在同一时刻10 台机器上会分别有一个线程去冲击数据库。虽然每台机器做到了单飞但对于后端 DB 来说依然承受了 $10 \times 1$ 的并发压力因此就要引出分布式的单飞机制。单 JVM 下的最佳实践与补救为了在单 JVM 下安全地使用单飞建议采取以下防范措施严格设置 Future 的 Get 超时时间绝对不要使用无限等待的future.get()而是使用future.get(500, TimeUnit.MILLISECONDS)。一旦超时快速降级返回熔断数据或旧缓存防止耗尽 JVM 线程池。使用 Guava / Caffeine 內建的单飞Java 中不建议自己手写单飞Caffeine 缓存的cache.get(key, k - loadFromDB(k))原生在底层就实现了单 JVM 内的单飞机制基于ConcurrentHashMap的锁分段和Node锁并且针对垃圾回收和并发性能做到了极致优化。结合异步刷新Refresh After Write让老数据先返回给用户后台启动一个异步线程去单飞加载新数据。这样用户线程永远不会阻塞彻底根治线程木僵和超时问题分布式Single-flight其实学这个单体和分布式和我们在点评中学的锁机制感觉有异曲同工之妙单体锁和分布式锁都是为了应对负载均衡集群部署的问题单机能力集群后为什么失效演进方案synchronized锁只能作用于当前 JVM分布式锁ReentrantLock只能锁本机线程分布式锁Single-flight只能去重本机请求分布式 Single-flight根本原因都是负载均衡后请求被分发到了不同的 JVM本地内存状态无法共享。唯一的区别是它们扩展的是不同的能力锁扩展的是互斥能力整个集群只能有一个执行者。Single-flight扩展的是请求去重能力整个集群相同请求只执行一次其他节点共享结果。所以演进原因相同扩展目标不同。因此明白了上面的类比我们再学习分布式的Single-Flight就很简单了用一句话来说就是为了解决在多台机器组成的集群环境下防止热点缓存失效时高并发流量穿透到后端确保整个集群在同一时刻仅有唯一一个请求去冲击数据库从而彻底避免数据库被冲垮其实就是通过共享那一次的查询结果来避免数据库被冲垮因果关系。再深入一下分布式系统的CAP理论在分布式系统中一致性、可用性和分区容错性三者不能同时完全满足系统设计必须在其中做出权衡。C (Consistency - 一致性)每次读取要么获得最新写入的数据要么报错。在分布式系统中这意味着所有节点在同一时间的数据必须完全一致就像访问单机单台数据库一样。A (Availability - 可用性)每次请求都能获得一个非错误的响应但不保证获取的是最新写入的数据。系统必须一直处于“可提供服务”的状态。P (Partition Tolerance - 分区容错性)尽管网络会丢失或延迟节点之间的任意数量的消息即发生网络分区系统仍能继续运行。所以为了实现在分布式环境下系统能够实现高可用基于CAP理论我们的分布式Single-flight分布式做了如下的设计核心设计理念与 CAP 取舍全局锁与状态机偏向 CP使用 Redis Lua 脚本保证争抢执行权的原子性。全网多个实例同时发起相同请求时系统强制保证只有一个 Leader 节点突围。此时为了防止“僵尸节点”脑裂引入Fencing Token防护令牌机制一旦原 Leader 假死超时立即剥夺其回写结果的权利。保证了在并发控制上对强一致性C。心跳续期与主动接管保证 A如果 Leader 节点真宕机了其他处于等待共享状态的 Follower 节点不能被永远阻塞。通过 Owner Heartbeat 机制一旦 Leader 心跳丢失Follower 迅速超时并重新发起选举接管请求。这保证了哪怕发生单点故障整个面试链路的可用性A依然不受影响。本地 L1 Cache 终极降级AP 兜底当遭遇极端网络故障严重的 PRedis 集群整体不可用时框架自动退化回本地单机 Single-flight 模式。此时系统主动放弃了全局一致的请求合并C但死保住了用户的核心面试流程不中断A。至于什么是状态机heartbeat机制等等我们放在后面研究。结语这里仅仅是简单的带大家了解了一下这种单飞机制下面我会继续深入的讲解这些底层原理以及一些其他的技术。