← 中文资源

简体中文版本

单飞

单飞把同一键的并发调用合并为一次执行,其他调用等待并共享结果,避免冷缓存下的重复昂贵操作。

阅读英文原文

惊群问题

高负载下的缓存未命中是经典故障模式:每个请求检查缓存、未命中、调用数据库,数据库在缓存刚失效时承受尖峰。单飞确保只有第一个请求执行查询,其余等待其结果。

type Entry<T> = { promise: Promise<T> };

class SingleFlight {
  private inflight = new Map<string, Entry<unknown>>();

  async run<T>(key: string, operation: () => Promise<T>): Promise<T> {
    const existing = this.inflight.get(key) as Entry<T> | undefined;
    if (existing) return existing.promise;

    const entry: Entry<T> = { promise: operation() };
    this.inflight.set(key, entry as Entry<unknown>);
    try {
      return await entry.promise;
    } finally {
      this.inflight.delete(key);
    }
  }
}

作用域与生命周期

键必须精确标识工作:缓存键、用户 ID 或令牌刷新身份。在 finally 中删除条目,避免失败污染后续调用。如果操作可能超过调用方截止时间,应支持取消,但不要取消其他等待者共享的工作。

何时使用

用于读穿缓存、DNS 或元数据解析、令牌刷新。与缓存相比,单飞减少并发重复工作,但不会让未来请求更快;与锁相比,它让所有调用方获得结果,而不必串行执行重复操作。

陷阱

有副作用的操作不应被去重,除非副作用本身幂等。把同一个可变对象返回给所有调用方会引入共享状态 bug。在多实例部署中,进程内 Map 仍允许每个实例调用一次;当后端无法承受这种扇出时,应结合分布式缓存或锁。

相关资源