基于本文回答

播面 播面

刷题像听歌,多听自然懂
0
评论

如何避免缓存雪崩、缓存击穿和缓存穿透在 Nginx 代理层的发生?

在 Nginx 代理层(通常作为网关或边缘缓存)处理缓存雪崩、击穿和穿透问题,是非常有效的一种架构防御手段。因为它可以将流量拦截在应用服务器和数据库之前。

以下是如何在 Nginx 层面上通过配置和架构设计来避免这三大缓存问题的具体方案:


1. 避免缓存穿透 (Cache Penetration)

现象: 客户端频繁请求一条数据库中根本不存在的数据。由于数据不存在,缓存也不会命中,导致每次请求都直接打到后端应用和数据库,常被黑客用于恶意攻击。

Nginx 层的防御策略:

  • 策略一:缓存空值 / 缓存 404 响应
    如果后端返回 404(表示数据不存在),Nginx 可以将这个 404 状态也缓存一小段时间,防止相同的无效请求持续打到后端。

    plaintext
    # 缓存 200 正常响应 10 分钟
    proxy_cache_valid 200 10m;
    # 核心:缓存 404 响应 1 分钟(时间不宜过长,防止数据真的新增后无法访问)
    proxy_cache_valid 404 1m; 
  • 策略二:请求限流 (Rate Limiting)
    针对恶意构造大量不同且不存在的 key 进行攻击的情况,通过 Nginx 的 limit_reqlimit_conn 限制单个 IP 的访问频率。

    plaintext
    # 在 http 块定义限流规则:每秒最多 10 个请求
    limit_req_zone $binary_remote_addr zone=mylimit:10m rate=10r/s;
    
    server {
        location /api/ {
            # 允许突发 20 个请求,超过则返回 503
            limit_req zone=mylimit burst=20 nodelay; 
            proxy_pass http://backend;
        }
    }
  • 策略三:结合 OpenResty (Lua) + 布隆过滤器 (Bloom Filter)
    这是最彻底的解决方式。利用 OpenResty 在 Nginx 层嵌入 Lua 脚本,请求到达时,先通过 Lua 脚本查询 Redis 中的布隆过滤器。如果布隆过滤器判断 ID 不存在,Nginx 直接返回 404,完全不碰后端。


2. 避免缓存击穿 (Cache Breakdown)

现象: 一个极其热点的数据在缓存中突然过期,此时瞬间有大量的并发请求访问该 key,因为缓存失效,这些请求会同时穿透到后端数据库,导致数据库瞬间压力过大甚至宕机。

Nginx 层的防御策略:

  • 策略一:开启缓存锁 (proxy_cache_lock)
    Nginx 提供了合并回源请求的功能。当多个客户端同时请求同一个缓存未命中的 key 时,Nginx 只允许第一个请求去后端拉取数据并更新缓存,其他请求会等待。

    plaintext
    location /api/ {
        proxy_cache my_cache;
        
        # 开启缓存锁,只允许一个请求回源
        proxy_cache_lock on;
        
        # 锁超时时间,如果 5 秒内回源请求没回来,放行下一个请求去回源
        proxy_cache_lock_timeout 5s; 
        
        # 如果回源请求超过 5 秒未响应,是否允许其他请求也去回源 (可选)
        proxy_cache_lock_age 5s;
        
        proxy_pass http://backend;
    }
  • 策略二:允许使用过期缓存 (proxy_cache_use_stale updating)
    结合后台更新,当缓存过期,第一个请求去后端拉取新数据时,其他的并发请求直接返回旧的过期数据,而不是等待,从而保证极高的并发响应能力。

    plaintext
    location /api/ {
        proxy_cache my_cache;
        # 当缓存正在更新时 (updating),其他请求使用过期缓存
        proxy_cache_use_stale updating; 
        proxy_pass http://backend;
    }

3. 避免缓存雪崩 (Cache Avalanche)

现象: 大量的缓存数据在同一时间集中过期,或者后端服务宕机,导致海量请求瞬间全部打向后端数据库。

Nginx 层的防御策略:

  • 策略一:打散缓存过期时间 (随机过期时间)
    Nginx 原生的 proxy_cache_valid 只能设置固定时间(如 10m),无法直接设置随机时间。在 Nginx 层解决这个问题有两种方法:

    1. 依赖后端控制: Nginx 默认会遵守后端应用返回的 HTTP 头(如 Cache-Control: max-age=X)。后端可以在生成响应时,给 max-age 加上一个随机的抖动值(例如 10分钟 ± 2分钟),Nginx 会按照这个随机时间来缓存。
    2. OpenResty/Lua 动态设置: 使用 Lua 脚本在 Nginx 层面动态生成并覆盖后端返回的过期时间。
  • 策略二:后端故障时的兜底策略 (降级容灾)
    如果发生雪崩导致后端应用响应极慢、超时或报错 5xx,Nginx 可以直接将之前缓存的过期数据返回给用户,保证服务可用性(降级)。

    plaintext
    location /api/ {
        proxy_cache my_cache;
        # 当后端返回 error, timeout, 或者 500/502/503/504 时,返回过期的旧缓存!
        proxy_cache_use_stale error timeout http_500 http_502 http_503 http_504;
        proxy_pass http://backend;
    }
  • 策略三:后台异步更新缓存 (proxy_cache_background_update)
    当客户端请求一个过期的缓存时,Nginx 立即将过期的旧内容返回给客户端,同时在后台偷偷发起一个子请求去后端获取新数据并更新缓存。这彻底消除了客户端因为等待后端响应而产生的延迟,也防止了后端被突发流量打垮。

    plaintext
    location /api/ {
        proxy_cache my_cache;
        
        # 开启后台更新
        proxy_cache_background_update on; 
        
        # 必须配合 updating 使用,表明在后台更新时,前端使用过期数据
        proxy_cache_use_stale updating; 
        
        proxy_pass http://backend;
    }

总结:一份高可用的 Nginx 缓存配置模板

综合以上策略,一份防御力较强的 Nginx 代理配置如下:

plaintext
http {
    # 定义缓存路径和规则
    proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=my_cache:10m max_size=10g inactive=60m use_temp_path=off;
    
    # 防穿透:限流规则
    limit_req_zone $binary_remote_addr zone=api_limit:10m rate=20r/s;

    server {
        listen 80;
        server_name example.com;

        location /api/ {
            # 1. 基础缓存配置
            proxy_cache my_cache;
            proxy_cache_key "$scheme$request_method$host$request_uri";

            # 2. 防穿透:缓存 404
            proxy_cache_valid 200 302 10m;
            proxy_cache_valid 404 1m;
            
            # 2. 防穿透:请求限流
            limit_req zone=api_limit burst=50 nodelay;

            # 3. 防击穿:开启缓存锁,合并回源请求
            proxy_cache_lock on;
            proxy_cache_lock_timeout 5s;

            # 4. 防击穿/防雪崩:允许使用陈旧数据(后台更新时、后端报错时)
            proxy_cache_use_stale updating error timeout http_500 http_502 http_503 http_504;
            
            # 5. 防雪崩:后台异步更新
            proxy_cache_background_update on;

            # 代理到后端
            proxy_pass http://backend_upstream;
        }
    }
}

通过这套配置,即使后端应用层或 Redis 层面临巨大的瞬间压力,Nginx 代理层也能作为一层坚固的盾牌,依靠缓存锁、异步更新、降级旧数据空值缓存等机制,确保整个系统的高可用性。

00:00
00:00