高并发系统中,如何避免惊群效应

惊群(Thundering Herd Problem) 是并发系统中的一种典型问题,指大量等待同一事件的线程、进程或请求,在事件发生时同时被唤醒,争抢同一资源,导致系统性能急剧下降。

本质上就是: 本来只需要一个人去处理,结果所有人都一起冲过去。


举例说明

假设有1000个客户端访问缓存。缓存中没有数据(Cache Miss),于是:

1000 个请求
        │
        ▼
   查询 Redis
        │
        ▼
   Redis 没有
        │
        ▼
1000 个请求同时访问 MySQL

结果:

  • MySQL 瞬间承受1000倍压力
  • 大量连接建立
  • CPU 飙升
  • IO 饱和
  • 最终数据库可能被打垮

这就是缓存击穿导致的惊群


Linux中最经典的惊群

例如多个进程都阻塞在:

1
accept(sockfd, ...)
            listen socket
                 │
     ┌───────────┼───────────┐
     │           │           │
 Process1    Process2    Process3
     │           │           │
   accept()    accept()    accept()
     │           │           │
      全部睡眠等待连接

当一个新的 TCP 连接到来时:

        新连接
           │
           ▼
 内核唤醒所有 accept()

结果:

Process1 醒了
Process2 醒了
Process3 醒了
...

最后:

只有一个拿到连接

其它全部:

accept()
↓

EAGAIN

↓

再次睡眠

一次连接:

唤醒100个进程
↓

99个白醒
↓

CPU白白调度99次

这就是 Linux 早期著名的 **Accept Thundering Herd(惊群)**。


epoll 中也可能出现

例如:

100 个 worker

都 epoll_wait()

监听同一个 fd

有一个事件到来:

内核:

把100个worker全部唤醒

最后:

只有一个成功 read()

其它99个:

read()

↓

EAGAIN

CPU 做了大量无效上下文切换


Cache Stampede(缓存惊群)

互联网最常见的是:缓存过期

例如:

商品详情

TTL = 5分钟

5分钟到了:

10000 个请求同时访问

↓

缓存全部 Miss

↓

10000 个请求全部查数据库

数据库压力骤增。

解决方案:

  • 热点数据永不过期(逻辑过期)
  • 单飞(SingleFlight):只有一个请求回源,其它请求等待
  • 请求合并(Request Coalescing)
  • TTL 加随机抖动(Jitter),避免大量键同时过期
  • 互斥锁(如 Redis 分布式锁)

Kubernetes 中的惊群

例如:

Deployment

1000 Pods

同时:

收到 ConfigMap 更新

于是:

1000 Pods

↓

同时 Reload

↓

同时连接数据库

↓

数据库瞬间建立1000连接

或者:

1000 Pods

↓

同时访问 Apollo

↓

Apollo 瞬间压力暴涨

Docker Swarm 中

例如:

docker service update

如果:

--update-parallelism = 0

表示:所有容器一起更新

于是:

100 个容器

↓

同时启动

↓

同时初始化

↓

同时连 Redis

↓

同时连 MySQL

↓

惊群

因此通常会设置:

--update-parallelism 2
--update-delay 10s

实现滚动更新,降低瞬时负载


惊群常见解决方案

场景 解决方案
accept 惊群 SO_REUSEPORT、现代 Linux 内核优化、Nginx 的 accept_mutex(较早版本)
epoll 惊群 EPOLLEXCLUSIVE(Linux 4.5+)
缓存惊群 SingleFlight、互斥锁、逻辑过期、TTL 随机化
数据库惊群 连接池、限流、请求合并
微服务惊群 熔断、限流、指数退避(Exponential Backoff)、随机退避(Jitter)
容器更新惊群 滚动发布、分批重启、预热(Warm-up)

本质

惊群问题的根源可以归纳为三个阶段:

多个等待者
        │
        ▼
同一事件发生
        │
        ▼
全部同时苏醒
        │
        ▼
竞争同一资源
        │
        ▼
只有极少数成功
        │
        ▼
大量线程/进程再次休眠

因此,惊群的核心不是“请求很多”,而是大量等待者因同一个事件被同时唤醒,造成无效竞争、频繁上下文切换以及资源瞬时争抢。现代系统通常通过减少无效唤醒(如 EPOLLEXCLUSIVE)、让请求串行化或合并(如 SingleFlight)、错峰(随机退避、TTL 抖动、滚动更新)等方式来缓解这一问题。