首页 / 帮助文档 / CC攻击防护动态页面缓存减轻源站压力

CC攻击防护动态页面缓存减轻源站压力

CC攻击本质上拼的是资源消耗速度。攻击者用大量看似合法的请求打满源站连接数、CPU和带宽,而动态页面每次请求都要走完整的后端逻辑,数据库查询、模板渲染、会话读写一样不少,源站很容易在几分钟内被打到不可用。把动态页面做缓存,让大部分请求在边缘节点或缓存层直接返回,是当前防护体系里最直接有效的一种减压手段。

很多人一听到“动态页面缓存”就觉得不可行,认为动态内容每个用户看到的不一样,缓存了会出问题。实际上动态页面里存在大量可复用的片段和短暂固定的完整输出。比如新闻详情页、商品介绍页、分类列表、搜索结果的第一页,这些内容在几秒到几分钟内对所有匿名用户完全一致,完全可以整页缓存。真正因人而异的部分,比如购物车数量、登录状态、用户名,可以通过前端异步加载或ESI片段拼接的方式处理,不必让整个页面都穿透到源站。

实现动态页面缓存的核心思路是分级。第一级是边缘节点整页缓存,把匿名用户的GET请求在CDN或反向代理层直接返回,缓存时间根据内容更新频率设定,新闻类可以设30秒到2分钟,商品详情可以设5到10分钟。第二级是应用层片段缓存,把页面中变化频率低、计算成本高的模块单独缓存,比如推荐位、热门榜单、评论区前几条。第三级是源站内部对象缓存,比如数据库查询结果、API响应、模板渲染片段,用Redis或Memcached存储,减少重复计算。三级配合起来,源站实际处理的请求量可以下降80%以上。

要让动态缓存真正生效,缓存键的设计非常关键。不能只按URL缓存,必须把影响页面输出的变量纳入键值。常见的做法是把URL、查询参数排序后的字符串、设备类型、语言、货币、A/B测试分组、是否登录等维度组合起来生成缓存键。如果某个参数对页面内容没有影响,比如各种追踪参数utm_source、fbclid,应该在代理层就剥离掉,避免同一个页面因为参数不同生成几十份缓存,浪费存储还降低命中率。剥离无用参数这件事,建议在边缘节点配置规则统一处理,比在应用层处理更早、更省资源。

缓存策略上,匿名用户和登录用户要分开对待。匿名用户流量大、页面差异小,适合长一点的整页缓存。登录用户流量相对小,但页面个性化强,整页缓存容易串数据,更适合做片段缓存和对象缓存。如果业务上登录用户也必须整页缓存,那就要在缓存键里加入用户ID或用户组标识,同时把敏感数据从缓存页面里抽离出来,用异步接口单独下发。安全上还要注意,带Set-Cookie响应的页面不要缓存,否则会导致会话固定或用户信息泄露。

CC攻击期间,动态缓存还能配合“缓存锁”机制进一步保护源站。正常情况下,缓存过期后第一个请求会穿透到源站取新内容,后面的请求等待或拿旧内容。攻击者如果专门挑缓存过期的时间窗口打,还是能对源站形成脉冲压力。缓存锁的做法是:缓存过期后,只允许一个请求去源站回源,其他请求直接返回旧缓存内容,或者等待新缓存生成。这样即使攻击者精确卡点,源站每秒也只会收到极少量回源请求,不会被打垮。Nginx和部分CDN都支持类似的stale-while-revalidate和stale-if-error配置,能显著平滑源站负载。

具体到Nginx层面,一个实用的动态页面缓存配置可以这样写:

proxy_cache_path /tmp/nginx_cache levels=1:2 keys_zone=dynamic_cache:100m max_size=10g inactive=60m use_temp_path=off;

server {
    listen 80;
    server_name example.com;

    set $cache_key "$scheme$request_method$host$request_uri";

    location / {
        proxy_pass http://backend;
        proxy_cache dynamic_cache;
        proxy_cache_key $cache_key;
        proxy_cache_valid 200 60s;
        proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504;
        proxy_cache_lock on;
        proxy_cache_lock_timeout 5s;
        proxy_cache_bypass $cookie_session;
        proxy_no_cache $cookie_session;
        add_header X-Cache-Status $upstream_cache_status;
    }
}

这个配置里,proxy_cache_lock on就是缓存锁,proxy_cache_use_stale让缓存过期或源站出错时继续用旧内容,proxy_cache_bypass和proxy_no_cache针对带会话Cookie的请求跳过缓存,避免登录用户拿到错误内容。实际部署时,缓存目录、内存大小和缓存时间要根据业务量调整,同时要监控缓存命中率和回源请求数,命中率低于70%就说明缓存键或缓存策略需要优化。

动态页面缓存还有一个容易被忽略的价值:它可以作为源站过载时的“降级开关”。当源站CPU使用率超过阈值或者数据库连接数打满时,运维可以把缓存时间从60秒临时调大到10分钟甚至30分钟,让边缘节点承担几乎全部流量,源站只处理极少量回源请求。这种降级对用户几乎无感,因为页面内容只是稍微旧一点,但可用性保住了。相比直接返回静态维护页,这种“软降级”对业务影响小得多,也更能维持用户体验和SEO表现。

做动态缓存时,内容更新机制必须配套。如果页面缓存了10分钟,但后台改了价格或库存,用户可能在10分钟内看到旧数据。解决这个问题有两种常见做法:一是主动失效,后台内容更新时通过API调用CDN或代理层的purge接口,把相关URL的缓存清掉;二是设置合理的缓存时间,让数据延迟在业务可接受范围内。对于价格、库存这类敏感字段,建议不要整页缓存,而是用异步接口实时获取,页面主体缓存起来,敏感数据单独请求,这样既保证性能又保证数据准确。

从防护效果看,动态页面缓存对CC攻击的缓解作用体现在三个层面。第一,边缘节点直接吸收了大部分请求,源站连接数和带宽消耗大幅下降。第二,缓存锁和旧内容兜底机制让攻击者无法通过缓存穿透制造回源高峰。第三,即使攻击流量完全打满边缘节点,源站依然能保持低负载运行,核心数据库和应用服务器不会因为连接耗尽而崩溃。这种架构上的抗压能力,比单纯靠防火墙封IP要可靠得多,因为CC攻击的源IP可以不断变化,而缓存命中率不会因为IP变化而下降。

当然,动态缓存不是万能的。对于POST请求、带文件上传的接口、实时性要求极高的交易接口,不能简单套用整页缓存。这些接口需要从业务逻辑层面做限流、异步化和队列削峰。但一个网站的大部分流量通常集中在少数几个高访问量的GET页面上,把这些页面缓存好,就能挡住大部分CC攻击的冲击。剩下的敏感接口再配合频率限制、验证码和行为分析,整体防护体系就完整了。

最后要强调的是,动态页面缓存需要持续调优。上线后要定期分析缓存命中率、回源比例、缓存过期时间和源站负载的关系,根据业务变化调整缓存时间和缓存键规则。攻击期间可以临时放大缓存时间,攻击结束后再恢复。把动态缓存当作一个可以随时调节的流量调节阀,而不是一次配置就再也不动的静态设置,这样才能在CC攻击面前始终掌握主动权。