本来在查资料,结果发现自己博客图片刷不出来了。看了一下账单,好家伙,CDN 流量半小时跑了 5G。
日志拉下来一分析,某个爬虫正在丧心病狂地遍历下载我博客所有的附件目录,而且 User-Agent 伪装成了正常的 Chrome 浏览器,靠封 UA 根本封不掉。
既然分不清你是人是爬虫,那就直接按 IP 限制并发频率。这篇文章记录我用 Nginx 漏桶算法限流的过程。
什么是漏桶算法
漏桶算法(Leaky Bucket)是一种流量整形算法。想象一个底部有洞的桶,水(请求)以任意速度倒入桶中,但流出的速度是固定的。如果倒入的速度太快,桶就会满,多出来的水就溢出了。
在 Nginx 里,这个"桶"就是内存中的一块区域,用来记录每个 IP 的请求频率。请求进来先放到桶里,如果频率超过设定值,就直接拒绝服务。
Nginx 通过 limit_req_zone 和 limit_req 两个指令实现漏桶限流。
配置 limit_req_zone
首先在 Nginx 配置文件的 http 块里定义限流区域:
1http {
2 # 定义一个名为 anti_spider 的限流区域
3 # $binary_remote_addr 表示用客户端 IP 作为 key
4 # 10m 表示分配 10MB 内存存储状态
5 # rate=2r/s 表示每个 IP 每秒最多 2 个请求
6 limit_req_zone $binary_remote_addr zone=anti_spider:10m rate=2r/s;
7
8 # 其他配置...
9}
参数说明:
$binary_remote_addr:客户端 IP 的二进制形式,比字符串形式更省内存。zone=anti_spider:10m:区域名称叫 anti_spider,分配 10MB 内存。rate=2r/s:每个 key(这里是每个 IP)每秒允许 2 个请求。
10MB 内存大概能存 16 万个 IP 的状态,对个人博客来说完全够用。
在 location 里应用限流
定义好区域后,在需要限流的 location 里使用 limit_req:
1server {
2 listen 80;
3 server_name blog.example.com;
4
5 location /images/ {
6 limit_req zone=anti_spider burst=5 nodelay;
7 alias /var/www/blog/images/;
8 expires 30d;
9 }
10}
zone=anti_spider:使用刚才定义的限流区域。burst=5:允许突发 5 个请求。也就是说,前 5 个请求可以先进桶里排队。nodelay:不延迟处理,超过速率的请求直接返回 503,而不是排队等待。
对于图片、附件这些静态资源,用 nodelay 比较合理。爬虫每秒请求几百次,让它排队没意义,直接拒绝最干脆。
实际效果
重新加载配置后,我自己用脚本并发请求了一波图片做测试:
1ab -n 100 -c 10 https://blog.example.com/images/test.jpg
结果前几个请求返回 200,后面的全部返回 503。这说明限流生效了。
再看监控,那个疯狂的爬虫依然在尝试抓取,但全被 Nginx 拒之门外,带宽占用瞬间回落到正常水平。
全局限流 vs 局部限流
上面是按 location 限流,只针对 /images/ 目录。如果你发现全站都被爬,可以在 server 块里全局应用:
1server {
2 limit_req zone=anti_spider burst=10 nodelay;
3
4 location / {
5 proxy_pass http://localhost:8080;
6 }
7}
但全局限流要慎重,因为正常用户如果点得快,也可能触发限制。建议先针对静态资源目录限流,观察效果再决定是否扩大范围。
配合日志分析
限流之后,建议把被拒绝的请求记录到单独日志里,方便分析:
1limit_req_zone $binary_remote_addr zone=anti_spider:10m rate=2r/s;
2
3server {
4 location /images/ {
5 limit_req zone=anti_spider burst=5 nodelay;
6 error_page 503 = @rate_limited;
7 }
8
9 location @rate_limited {
10 access_log /var/log/nginx/rate_limit.log main;
11 return 503;
12 }
13}
通过分析 rate_limit.log,可以看到哪些 IP 在大量请求,必要时再用防火墙封禁。
另一个维度:限制连接数
除了限制请求频率,Nginx 还可以限制并发连接数,用的是 limit_conn_zone 和 limit_conn:
1http {
2 limit_conn_zone $binary_remote_addr zone=addr:10m;
3
4 server {
5 location / {
6 limit_conn addr 10;
7 }
8 }
9}
这表示每个 IP 同时最多只能建立 10 个连接。对于防止某些下载工具开大量线程很有用。
总结
这次被爬虫偷带宽的经历让我学到了几件事:
- 不要只依赖 User-Agent 判断爬虫,现在很多爬虫会伪装 UA。
- 按 IP 限流是比较通用的防御手段,对人影响小,对爬虫效果明显。
limit_req_zone配置简单,但效果很好。- 限流后要监控日志,看看是哪些 IP 在搞事情。
如果你也运营一个小网站,建议提前把 Nginx 限流配置好。等真正被爬的时候再临时应对,可能已经损失了不少流量费用。