很久没写博文了,昨晚睡了不足4个小时结果还没睡好,缘由是女友跟我分手,这辈子最真爱的一个。html
不说了那还得继续生活奋斗,最近解决公司CC***问题,头大的要死。前端
由于架构缘由要在前端nginx代理解决这个问题,没有硬件只能软件的解决这个问题。node
Nginx 有 2 个模块用于控制访问“数量”和“速度”,简单的说,控制你最多同时有 多少个访问,而且控制你每秒钟最多访问多少次, 你的同时并发访问不能太多,也不能太快,否则就“杀无赦”。nginx
HttpLimitZoneModule 限制同时并发访问的数量shell
HttpLimitReqModule 限制访问数据,每秒内最多几个请求后端
limit_conn_zone
语法: limit_conn_zone $variable zone=name:size;
默认值: none
配置段: http
该指令描述会话状态存储区域。键的状态中保存了当前链接数,键的值能够是特定变量的任何非空值(空值将不会被考虑)。$variable定义键,zone=name定义区域名称,后面的limit_conn指令会用到的。size定义各个键共享内存空间大小。如:tomcat
limit_conn_zone $binary_remote_addr zone=addr:10m;
bash
注释:客户端的IP地址做为键。注意,这里使用的是$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 (Service Temporarily Unavailable) 错误。
limit_zone 指令和limit_conn_zone指令同等意思,已经被弃用,就再也不作说明了。服务器
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 (Service Temporarily Unavailable)错误。如:
limit_conn_zone $binary_remote_addr zone=addr:10m; |
server { |
location /www.hzcsky.com/ { |
limit_conn addr 1; |
} |
} |
同一IP同一时间只容许有一个链接。
当多个 limit_conn 指令被配置时,全部的链接数限制都会生效。好比,下面配置不只会限制单一IP来源的链接数,同时也会限制单一虚拟服务器的总链接数:
limit_conn_zone $binary_remote_addr zone=perip:10m; |
limit_conn_zone $server_name zone=perserver:10m; |
server { |
limit_conn perip 10; |
limit_conn perserver 100; |
} |
[warning]limit_conn指令能够从上级继承下来。[/warning]
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倍。
----------------------------------------------------------------------------
limit_req_zone
语法: limit_req_zone $variable zone=name:size rate=rate;
默认值: none
配置段: http
设置一块共享内存限制域用来保存键值的状态参数。 特别是保存了当前超出请求的数量。 键的值就是指定的变量(空值不会被计算)。如
limit_req_zone $binary_remote_addr zone=one:10m rate=1r/s; |
说明:区域名称为one,大小为10m,平均处理的请求频率不能超过每秒一次。
键值是客户端IP。
使用$binary_remote_addr变量, 能够将每条状态记录的大小减小到64个字节,这样1M的内存能够保存大约1万6千个64字节的记录。
若是限制域的存储空间耗尽了,对于后续全部请求,服务器都会返回 503 (Service Temporarily Unavailable)错误。
速度能够设置为每秒处理请求数和每分钟处理请求数,其值必须是整数,因此若是你须要指定每秒处理少于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_log_level notice”, 延迟的日志就是info级别。
limit_req_status
语法: limit_req_status code;
默认值: limit_req_status 503;
配置段: http, server, location
该指令在1.3.15版本引入。设置拒绝请求的响应状态码。
limit_req
语法: limit_req zone=name [burst=number] [nodelay];
默认值: —
配置段: http, server, location
设置对应的共享内存限制域和容许被处理的最大请求数阈值。 若是请求的频率超过了限制域配置的值,请求处理会被延迟,因此全部的请求都是以定义的频率被处理的。 超过频率限制的请求会被延迟,直到被延迟的请求数超过了定义的阈值,这时,这个请求会被终止,并返回503 (Service Temporarily Unavailable) 错误。这个阈值的默认值为0。如:
limit_req_zone $binary_remote_addr zone=limit_com:10m rate=1r/s; |
server { |
location /www.hzcsky.com.com/ { |
limit_req zone=limit_com burst=5; |
} |
} |
限制平均每秒不超过一个请求,同时容许超过频率限制的请求数很少于5个。
若是不但愿超过的请求被延迟,能够用nodelay参数,如:
limit_req zone=ttlsa_com burst=5 nodelay; |
--------完成配置事例--------------------------------------------
## 用户的 IP 地址 $limit 做为 Key,每一个 IP 地址最多有 50 个并发链接
## 你想开 几千个链接 刷死我? 超过 50 个链接,直接返回 503 错误给你,根本不处理你的请求了
当,然这是都是ngin来处理,不会影响后端的tomcat等WEB 应用 ,若是nginx网卡流量堵塞和单台压力问题就的想别的办法了。后面会说在解决的
limit_req_zone $limit zone=tlcy_com:10m rate=10r/s;
limit_req_log_level info;
limit_conn_zone $limit zone=addr:10m;
limit_conn_log_level info;
## 用户的 IP 地址 $limit 做为 Key,每一个 IP 地址每秒处理 10 个请求
## 你想用程序每秒几百次的刷我,没戏,再快了就不处理了,直接返回 503 错误给你
## 具体服务器配置
http{.... limit_req_zone $limit zone=tlcy_com:10m rate=10r/s; limit_req_log_level info; limit_conn_zone $limit zone=addr:10m; limit_conn_log_level info; server { listen 80; server_name www.hzcsky.com; if ($http_user_agent ~* LWP::Simple|BBBike|wget|Sosospider|YodaoBot) { return 403; } ## root /data/www/; ## index hou.txt; location /mp4/ { if ($request_method !~ ^(GET|HEAD|POST)$ ) { return 444; } } location / { if ($request_method !~ ^(GET|HEAD)$ ) { return 444; } proxy_next_upstream http_502 http_504 error timeout invalid_header; proxy_pass http://tlcy; proxy_redirect off; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; allow all; } ## 最多 5 个排队, 因为每秒处理 10 个请求 + 5个排队,你一秒最多发送 15 个请求过来, 再多就直接返回 503 错误给你了 limit_req zone=tlcy_com burst=5 nodelay; limit_conn addr 10; location ~* \.(gif|jpg|png|swf|flv)$ { valid_referers none blocked www.hzcsky.com ; if ($invalid_referer) { rewrite ^/ http://www.hzcsky.com/403.html; #return 404; } } }
事务都具备两面性的。ngx_http_limit_conn_module 模块虽然说能够解决当前面临的并发问题,可是会引入另一些问题的。如前端若是有作LVS或反代,而咱们后端启用了该模块功能,那不是很是多503错误了? 这样的话,能够在前端启用该模块,要么就是设置白名单。
------------------------------白名单设置-----------------------------------------
## 具体服务器配置 http{.... geo $white_ip { default 1; 127.0.0.1 0; 10.0.0.0/8 0; } #白名单 map $white_ip $limit { 1 $binary_remote_addr; 0 ""; } limit_req_zone $limit zone=tlcy_com:10m rate=10r/s; limit_req_log_level info; limit_conn_zone $limit zone=addr:10m; limit_conn_log_level info; server { listen 80; server_name www.hzcsky.com; if ($http_user_agent ~* LWP::Simple|BBBike|wget|Sosospider|YodaoBot) { return 403; } ## root /data/www/; ## index hou.txt; location /mp4/ { if ($request_method !~ ^(GET|HEAD|POST)$ ) { return 444; } } location / { if ($request_method !~ ^(GET|HEAD)$ ) { return 444; } proxy_next_upstream http_502 http_504 error timeout invalid_header; proxy_pass http://tlcy; proxy_redirect off; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; allow all; } ## 最多 5 个排队, 因为每秒处理 10 个请求 + 5个排队,你一秒最多发送 15 个请求过来, 再多就直接返回 503 错误给你了 limit_req zone=tlcy_com burst=5 nodelay; limit_conn addr 10; location ~* \.(gif|jpg|png|swf|flv)$ { valid_referers none blocked www.hzcsky.com ; if ($invalid_referer) { rewrite ^/ http://www.hzcsky.com/403.html; #return 404; } } }
#################若是 单台nginx解决不了前面须要LVS或者haproxy 了作4层 而后nginx多个 来解决这个问题,若是多台的 由于过来的全是LVS 的IP 这须要很麻烦的解决了。
(这里只说明结果,不了解 Http 协议的人请自行 Google 或者 Wikipedia http://zh.wikipedia.org/zh-cn/X-Forwarded-For )
当一个 CDN 或者透明代理服务器把用户的请求转到后面服务器的时候,这个 CDN 服务器会在 Http 的头中加入 一个记录
X-Forwarded-For : 用户IP, 代理服务器IP
若是中间经历了不止一个 代理服务器,像 www.bzfshop.net 中间创建多层代理以后,这个 记录会是这样
X-Forwarded-For : 用户IP, 代理服务器1-IP, 代理服务器2-IP, 代理服务器3-IP, ….
能够看到通过好多层代理以后, 用户的真实IP 在第一个位置, 后面会跟一串 中间代理服务器的IP地址,从这里取到用户真实的IP地址,针对这个 IP 地址作限制就能够了,
取得用户的原始地址
日志开启显示 :
log_format main '$http_x_forwarded_for $remote_addr - - $time_iso8601 "$request_method $scheme://$host$request_uri $server_protocol" $status $bytes_sent "$http_refe
rer" "$http_user_agent" $request_time $upstream_cache_status:TCP';
1 2 3 4 5 6 7 8 9 10 11 |
map $http_x_forwarded_for $clientRealIp { ## 没有经过代理,直接用 remote_addr "" $remote_addr; ## 用正则匹配,从 x_forwarded_for 中取得用户的原始IP ## 例如 X-Forwarded-For: 202.123.123.11, 208.22.22.234, 192.168.2.100,... ## 这里第一个 202.123.123.11 是用户的真实 IP,后面其它都是通过的 CDN 服务器 ~^(?P<firstAddr>[0-9\.]+),?.*$ $firstAddr; }
## 经过 map 指令,咱们为 nginx 建立了一个变量 $clientRealIp ,这个就是 原始用户的真实 IP 地址, ## 不论用户是直接访问,仍是经过一串 CDN 以后的访问,咱们都能取得正确的原始IP地址 |
完整配置事例:
## 具体服务器配置 http{ map $http_x_forwarded_for $limit { "" $remote_addr; ~^(?P<firstAddr>[0-9\.]+),?.*$ $firstAddr; } # map $white_ip $limit { # 1 $clientRealIp; # 0 ""; # } limit_req_zone $limit zone=tlcy_com:10m rate=5r/s; limit_req_log_level info; limit_conn_zone $limit zone=addr:10m; limit_conn_log_level info; server { listen 80; server_name www.hzcsky.com; if ($http_user_agent ~* LWP::Simple|BBBike|wget|Sosospider|YodaoBot) { return 403; } ## root /data/www/; ## index hou.txt; location /mp4/ { if ($request_method !~ ^(GET|HEAD|POST)$ ) { return 444; } } location / { if ($request_method !~ ^(GET|HEAD)$ ) { return 444; } proxy_next_upstream http_502 http_504 error timeout invalid_header; proxy_pass http://tlcy; proxy_redirect off; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; allow all; } ## 最多 5 个排队, 因为每秒处理 10 个请求 + 5个排队,你一秒最多发送 15 个请求过来, 再多就直接返回 503 错误给你了 limit_req zone=tlcy_com burst=5 nodelay; limit_conn addr 10; location ~* \.(gif|jpg|png|swf|flv)$ { valid_referers none blocked www.hzcsky.com ; if ($invalid_referer) { rewrite ^/ http://www.hzcsky.com/403.html; #return 404; } } }
由于他限制的是 原始IP 不用设置白名单之类了就。本身人能够直接访问nginx层,是原始IP地址没有限制就。
还要合理的封掉IP 就须要日志配合脚本了:
#!/bin/bash #取得参数$1为并发阈值,若留空则默认容许单IP最大200并发! if [[ -z $1 ]];then num=200 else num=$1 fi LOG=/root/log/nginx/sns_access.log STATUS=503 #请求检查、判断及拉黑主功能函数 function check(){ iplist=`cat $LOG |grep -i $STATUS |grep -i "," |awk '{print $2}' |grep -v "-" |sed "s#,##g" |sort |uniq -cd |sort -rn| awk -v str=$num '{if ($1>str){print $2}}'` if [[ ! -z $iplist ]]; then >/data/shell/black_ip.txt for black_ip in $iplist do #白名单过滤中已取消IP段的判断功能,可根据须要自行修改如下代码 #exclude_ip=`echo $black_ip | awk -F"." '{print $1"."$2"."$3}'` #grep -q $exclude_ip ./white_ip.txt grep -q $black_ip /data/shell/white_ip.txt if [[ $? -eq 0 ]];then echo "$black_ip (white_ip)" >>/data/shell/black_ip.txt else echo $black_ip >> /data/shell/black_ip.txt # iptables -nL | grep $black_ip ||(iptables -I INPUT -s $black_ip -j DROP & echo "$black_ip `date +%Y-%m-%H:%M:%S`">>/data/shell/denylog.txt ) fi done #存在并发超过阈值的单IP就发送邮件 # if [[ `cat ./sendmail` == 1 ]];then sendmsg;fi fi } function checka(){ iplist=`cat $LOG |grep -i $STATUS |grep -v "," |awk '{print $1}' |sort |uniq -cd |sort -rn | awk -v str=$num '{if ($1>str){print $2}}'` if [[ ! -z $iplist ]]; then >/data/shell/black_ip.txt for black_ip in $iplist do #白名单过滤中已取消IP段的判断功能,可根据须要自行修改如下代码 #exclude_ip=`echo $black_ip | awk -F"." '{print $1"."$2"."$3}'` #grep -q $exclude_ip ./white_ip.txt grep -q $black_ip /data/shell/white_ip.txt if [[ $? -eq 0 ]];then echo "$black_ip (white_ip)" >>/data/shell/black_ip.txt else echo $black_ip >> /data/shell/black_ip.txt # iptables -nL | grep $black_ip ||(iptables -I INPUT -s $black_ip -j DROP & echo "$black_ip `date +%Y-%m-%H:%M:%S`">>/data/shell #/denylog.txt ) fi done #存在并发超过阈值的单IP就发送邮件 # if [[ `cat ./sendmail` == 1 ]];then sendmsg;fi fi } #发邮件函数 function sendmsg(){ netstat -nutlp | grep "sendmail" >/dev/null 2>&1 || /etc/init.d/sendmail start >/dev/null 2>&1 echo -e "From: 发邮件地址@qq.com\nTo:收邮件地址@qq.com\nSubject:Someone Attacking your system!!\nIts Ip is" >./message cat ./black_ip.txt >>./message /usr/sbin/sendmail -f 发邮件地址@qq.com -t 收邮件地址@qq.com -i <./message >./sendmail } ##间隔10s无限循环检查函数 #while true #do # check # #每隔10s检查一次,时间可根据须要自定义 # sleep 10 #done # check #处理没有代理的IP checka #处理多层的IP
咱们日志是5分钟切割一次。因此就检查5分钟内非法的IP 给封掉,在写个2小时重置IPtables的计划任务 就好了。