高并发核心设计模式:Single-Flight 原理与实战

Single-Flight(单飞模式)是一种高级并发控制设计模式,主要用于防止缓存击穿(Cache Stampede)大幅减少对底层资源(如数据库、外部 API)的并发访问压力

其核心逻辑非常纯粹:在处理多个完全相同的并发请求时,只允许一个请求真正去执行昂贵的获取逻辑,其余请求在内存中阻塞等待。当唯一的代表请求执行完毕后,结果将被直接共享给所有等待的请求。

核心痛点:缓存击穿

在典型的高并发架构中,通常采用 Redis 作为缓存层,MySQL 作为持久层。

假设极其热门的“秒杀商品 A”缓存在 Redis 中。某一瞬间,该缓存过期或被意外逐出。紧接着的下一秒,有 1000 个用户的并发请求同时涌入查询该商品。

  • 如果没有 Single-Flight:这 1000 个请求在缓存中扑空后,会同时发起对 MySQL 的查询。这种瞬间的流量尖峰极易打满数据库的连接池,导致数据库由于瞬时压力飙升而宕机。
  • 引入 Single-Flight 后
    1. 第 1 个请求发现缓存未命中,准备向 MySQL 发起查询。同时,它会在本地生成一个标识(如以商品 ID 为 Key),记录“我正在查询商品 A”。
    2. 剩下的 999 个并发请求进入后,发现“商品 A”的查询任务已经在进行中,它们便不会去查 MySQL,而是原地阻塞等待
    3. 第 1 个请求从 MySQL 成功获取数据后,将数据回写缓存,并将该结果同时唤醒并分发给那 999 个等待的请求。
    4. 最终,系统仅对数据库发起了一次查询,却完美响应了 1000 个并发用户的请求。

底层工作原理

Single-Flight 的实现通常依赖于**哈希表(Map)并发等待机制(如锁或回调)**的结合:

  1. 接收到业务请求后,根据请求特征(如参数、路径)生成一个进程内唯一Key
  2. 检查本进程的 Map 中是否存在该 Key
    • 若不存在:当前线程为首发请求。将其 Key 注册进入 Map,随后去执行真正的底层业务逻辑(如查库)。
    • 若已存在:说明当前已有其他线程正在处理该 Key。当前线程无需重复执行,只需订阅并等待首发请求的执行结果。
  3. 首发请求执行完毕后,将结果广播通知所有等待该 Key 的同伴线程,并立即将该 Key 从 Map 中移除,以迎接未来可能的新一轮并发。

典型应用场景

除了最核心的防缓存击穿,它还广泛适用于以下场景:

  • 防止昂贵的重复计算:对于 CPU 密集型任务,当大量并发请求需要计算同一复杂规则时,可借此避免重复消耗算力。
  • 外部 API 调用保护与限流:当大量并发请求触发访问同一个收费或有严格频率限制的第三方外部接口时,将其合并为一个请求发出,有效节省调用配额并防止被远端限流。

业界常见实现

  • Go 语言:作为 Single-Flight 发扬光大的主阵地,Go 官方扩展包直接提供了标准实现 golang.org/x/sync/singleflight。在各类高级 Web 服务和微服务框架(如 go-zero)中被作为底层标配大量使用。
  • Java 生态:Java 标准库中虽无同名工具类,但在实战中,高级开发者常使用 ConcurrentHashMap 配合 CompletableFuture 或底层锁机制来实现同等并发语义。同时,业界顶级的本地缓存框架(如 Caffeine、Guava Cache)在底层其实已经内置了这种防击穿的合并机制。

速记知识卡片

核心维度 内容提要
设计本质 相同并发请求的内存级合并
解决核心痛点 缓存击穿(Cache Stampede)、底层资源耗尽
核心流程 生成 Key -> 查 Map -> 首发执行 / 后续阻塞等待 -> 结果广播共享 -> 清理 Key
适用边界 完全相同的读请求、无副作用的纯计算、外部 API 并发合并
底层依赖数据结构 线程安全的哈希表 + 阻塞唤醒机制
一句话速记 挡住千军万马的重复劳动,只派一个代表去探路返回结果