很久沒寫博文了,昨晚睡了不足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的計劃任務 就好了。