简述
在做日常的web运维工作中,难免会遇到服务器流量异常,负载过大等情况。
恶意攻击访问/爬虫等非正常性请求,会带来带宽的浪费,服务器压力增大,影响业务质量。
限流方案
对于这种情况, 一般可考虑通过nginx的ngx_http_limit_conn_module模块和ngx_http_limit_req_module 模块限制同一client ip的请求量和并发数来防范。
1. ngx_http_limit_conn_module模块(限制连接数[并发])
limit_conn_zone
- 语法: limit_conn_zone $variable zone=name:size;
- 默认值: none
- 配置段: http
$variable定义键(键的状态中保存了当前连接数),zone=name定义区域名称,后面的limit_conn指令会用到的。
size定义各个键共享内存空间大小(当共享内存空间被耗尽,服务器将会对后续所有的请求返回 503 错误。)。
limit_conn_log_level
- 语法:limit_conn_log_level info | notice | warn | error
- 默认值:error
- 配置段:http, server, location
- 当达到最大限制连接数后,记录日志的等级。
limit_conn
- 语法:limit_conn zone_name number
- 默认值:none
- 配置段:http, server, location
- 指定每个给定键值的最大同时连接数,当超过这个数字时被返回503
limit_conn_status
- 语法: limit_conn_status code;
- 默认值: limit_conn_status 503;
- 配置段: http, server, location
- 该指定在1.3.15版本引入的。指定当超过限制时,返回的状态码。默认是503。
limit_rate
- 语法:limit_rate rate
- 默认值:0
- 配置段:http, server, location, if in location
对每个连接的速率限制。参数rate的单位是字节/秒,设置为0将关闭限速。
按连接限速而不是按ip限制,因此如果某个客户端同时开启了两个连接,那么客户端的整体速率是这条指令设置值的2倍。
2. ngx_http_limit_req_module模块(限制请求数)
limit_req_zone
- 语法: limit_req_zone $variable zone=name:size rate=rate;
- 默认值: none
- 配置段: http
设置一块共享内存限制域用来保存键值的状态参数,速度可以设置为每秒处理请求数和每分钟处理请求数,其值必须是整数,所以如果你需要指定每秒处理少于1个的请求,2秒处理一个请求,可以使用 “30r/m”
limit_req_log_level
- 语法: limit_req_log_level info | notice | warn | error;
- 默认值: limit_req_log_level error;
- 配置段: http, server, location
设置你所希望的日志级别
limit_req_status
- 语法: limit_req_status code;
- 默认值: limit_req_status 503;
- 配置段: http, server, location
limit_req
- 语法: limit_req zone=name [burst=number] [nodelay];
- 默认值: —
- 配置段: http, server, location
设置对应的共享内存限制域和允许被处理的最大请求数阈值。 如果请求的频率超过了限制域配置的值,请求处理会被延迟,当被延迟的请求数超过了定义的阈值,这个请求会被终止,并返回503;nodelay:表示当超过访问次数并缓冲也满的情况下,直接放回503错误,若不设置,这些多余的请求会延迟处理 。
实际运用案例
一般情况下key使用$binary_remote_addr(建议使用)或者$remote_addr变量二者区别在于: $remote_addr变量的长度为7字节到15字节,而存储状态在32位平台中占用32字节或64字节,在64位平台中占用64字节。
$binary_remote_addr变量的长度是固定的4字节,存储状态在32位平台中占用32字节或64字节,在64位平台中占用64字节。
1m共享空间可以保存3.2万个32位的状态,1.6万个64位的状态。当恭喜内存空间被耗尽时,服务会对后续请求返回503(切记).
http{ ......... limit_conn_zone $binary_remote_addr zone=addr:1000m; limit_req_zone $binary_remote_addr zone=addrs:1000m rate=10r/s; #每秒请求10次 ........... server{ ...... limit_conn addr 10; #限制并发数为10 limit_req zone=addrs burst=5 nodelay; #允许超出请求5(排队/延迟) ...... } } #注 limit_conn_zone 和limit_req_zone可单独使用
除以上以$binary_remote_addr变量为key外,还有一种情况,如前端使用了代理层,cdn等服务,这种情况下$binary_remote_addr变量会是代理层/cdn的ip,就无法通过$binary_remote_addr变量做限制, 目前知道有两种解决方式,一种是使用白名单, 另外一种是配置$http_x_forwarded_for然后取逗号分隔的第一个ip(即源ip,不太好的消息就是这个变量字段是可以通过修改请求head信息进行伪造(curl -h "x-forwarded-for:192.168.0.1,192.168.0.2" http://127.0.0.1 ))。
白名单的方式后续再说。
这里先说下第二种方法, 可通过nginx的map模块做正则匹配后映射请求源ip, 具体配置如下:
1)代理层的配置:
upstream renren_trans_1 { server 10.5.11.12; }
server { listen 80; server_name zabbixtest.d.xiaonei.com; http_accounting_id zabbixtest; location / { proxy_pass http://renren_trans_1; proxy_set_header host $host; proxy_set_header x-real-ip $remote_addr; proxy_set_header x-forwarded-for $proxy_add_x_forwarded_for; client_max_body_size 50m; client_body_buffer_size 128k; client_body_temp_path /data/client_body_temp; proxy_connect_timeout 15; proxy_send_timeout 300; proxy_read_timeout 300; proxy_buffer_size 8k; proxy_buffers 4 32k; proxy_busy_buffers_size 64k; proxy_temp_file_write_size 64k; proxy_temp_path /data/proxy_temp; } }
代理层后端(业务机)配置:
map $http_x_forwarded_for $clientips { "" $remote_addr; #当为空时映射$remote_addr 到$clientips ~^(?p<firstip>[0-9\.]+),?.*$ $firstip; #获取第一个ip } limit_conn_zone $clientips zone=addr:1000m; limit_req_zone $clientips zone=addrs:1000m rate=10r/s; limit_req zone=addrs burst=5 nodelay; server { listen 80; server_name localhost; limit_conn addr 10; limit_req zone=addrs burst=5 nodelay; #charset koi8-r; #access_log logs/host.access.log main;
测试:
1) 测试通过代理层访问的方式:
# ab -c 11 -t 10 http://zabbixtest.d.xiaonei.com/test.html
access.log内容如下:
可发现大量503 error(第一列代理层,最后一列源ip) 。
2)测试直接访问,不通过代理层
# ab -c 5 -t 10 http://10.5.11.12/test.html
access.log如下:
同样会出现大量503 ,代理ip为空!
总结
以上为个人经验,希望能给大家一个参考,也希望大家多多支持代码网。
发表评论