针对爬虫引发流量激增的Varnish防护策略咨询
针对你遇到的分散IP爬虫触发大量未缓存请求打垮后端的问题,结合Varnish特性和Drupal站点的特点,给出以下可落地的方案:
1. 宽限期内单URL后端请求并发控制(解决你的第一个疑问)
Varnish可以在宽限期内限制同一个URL的后端刷新请求,确保只有一个请求去后端获取新内容,其他请求直接返回过期缓存(stale内容),避免后端被重复请求压垮。
在VCL中添加如下逻辑:
sub vcl_hit { if (obj.ttl <= 0s) { # 进入宽限期,尝试获取新内容 set req.http.X-Cache = "HIT-Stale"; set req.http.X-Cache-Status = "Stale-While-Revalidate"; # 仅允许一个请求去后端刷新,其余返回stale if (std.single_req(req.url)) { return (fetch); } else { return (deliver); } } return (deliver); } sub vcl_backend_response { # 根据更新频率设置合理宽限期(示例为4小时) set beresp.grace = 4h; }
std.single_req会确保同一个URL同时只有一个请求发往后端,其他请求直接返回当前的stale缓存,直到新内容刷新完成。
2. 基于URL前缀的请求速率限制(绕过IP分散问题)
既然爬虫IP分散,就放弃按IP限流,转而针对Term Pages的URL前缀(比如/taxonomy/term/)做请求频率限制,统计单位时间内某个URL的请求次数,超过阈值则返回stale内容。
借助Varnish的vmod_ratecounter实现:
import ratecounter; sub vcl_recv { # 匹配Term Pages路径 if (req.url ~ "^/taxonomy/term/[0-9]+$") { # 限制单个URL每分钟最多20次请求 if (ratecounter.check(req.url, 20, 60s)) { # 超过速率,直接尝试返回缓存(包括stale) return (lookup); } } }
这个逻辑会限制单个Term Page URL的请求频率,不管来自哪个IP,超过阈值的请求会优先走缓存,避免触发后端请求。
3. 基于后端负载动态调整缓存策略
当后端服务器负载过高时,自动延长宽限期,强制返回stale内容;负载恢复正常后再允许刷新缓存。
可以通过Varnish的backend状态或者外部监控数据实现:
sub vcl_hit { if (obj.ttl <= 0s) { # 检查后端健康状态或负载(示例为调用内部负载监控接口) if (backend.default.health == sick || std.fetch("http://localhost/backend-load") > 80) { # 后端负载高,延长宽限期至8小时,返回stale set obj.grace = 8h; return (deliver); } else { # 后端正常,允许刷新缓存 return (fetch); } } return (deliver); }
这里的std.fetch可以调用内部的负载监控接口(比如通过Prometheus导出的后端负载数据),动态调整缓存策略。
4. 爬虫识别与差异化缓存
通过请求特征识别爬虫,对爬虫返回stale内容,对正常用户优先刷新缓存:
sub vcl_recv { # 通过UA或请求特征识别爬虫 if (req.http.User-Agent ~ "(bot|crawler|spider|scraper)" || req.http.X-Forwarded-For ~ "爬虫IP段特征") { set req.http.X-Is-Crawler = "true"; } } sub vcl_hit { if (obj.ttl <= 0s) { # 爬虫请求直接返回stale内容 if (req.http.X-Is-Crawler == "true") { return (deliver); } # 正常用户请求触发缓存刷新 return (fetch); } return (deliver); }
也可以结合请求频率,比如某个IP在1分钟内请求超过10个不同的Term Page,标记为爬虫,后续请求直接返回stale。
5. 缓存主动预热
在Drupal节点更新触发缓存清除后,主动预热对应的Term Page,避免爬虫请求触发后端请求。
实现步骤:
- 在Drupal中编写自定义模块,监听节点更新事件,获取关联的Term ID
- 调用HTTP客户端批量请求这些Term Page的URL,让Varnish缓存起来
- 示例Drupal钩子代码:
/** * Implements hook_node_update(). */ function mymodule_node_update($node) { // 获取节点关联的Term ID $terms = taxonomy_term_load_multiple_by_name($node->field_tags->value, 'tags'); foreach ($terms as $term) { // 发起预热请求 $url = url('taxonomy/term/' . $term->tid, ['absolute' => TRUE]); drupal_http_request($url, ['headers' => ['Cache-Control' => 'no-cache']]); } }
这样在缓存清除后,主动将页面加载到Varnish缓存中,爬虫请求时直接命中缓存。
内容的提问来源于stack exchange,提问作者KBS

