開源生態新解法:Nginx+Lua實現毫秒級非法IP熔斷機制

連結:https://juejin.cn/post/7475693874510315583

背景:訪問時不時會被暴力刷量,爬蟲和惡意攻擊導致資料庫,服務等癱瘓。。需求:在Nginx上實現一個動態攔截IP的方法,具體是當某個IP在1分鐘內訪問超過60次時,將其加入Redis並攔截,攔截時間預設1天。

技術選型:使用Nginx+Lua+Redis的方法。這種方案透過Lua指令碼在Nginx處理請求時檢查Redis中的黑名單,同時統計訪問頻率,超過閾值就封禁。這應該符合使用者的需求。
需要結合Lua指令碼和Redis的計數功能。安裝OpenResty,配置Nginx的Lua模組,編寫Lua指令碼統計訪問次數,使用Redis儲存和過期鍵,以及設定攔截邏輯。連線池的使用,避免頻繁連線Redis影響效能。

一、環境準備

  1. 安裝OpenRestyOpenResty集成了Nginx和Lua模組,支援直接執行Lua指令碼:
    # Ubuntu/Debiansudo apt-get install openresty# CentOSyum install openresty
  2. 安裝Redis服務
    sudo apt-get install redis-server  # Debian系sudo yum install redis             # RedHat系

二、Nginx配置

  1. 主配置檔案(nginx.conf)http塊中新增共享記憶體和Lua指令碼路徑:
    http {    lua_package_path "/usr/local/openresty/lualib/?.lua;;";    lua_shared_dict ip_limit 10m;  # 共享記憶體區    server {        listen 80;        server_name _;        location / {            access_by_lua_file /usr/local/lua/ip_block.lua;  # 核心攔截指令碼            root /var/www/html;        }    }}

三、Lua指令碼實現動態攔截

  1. 指令碼路徑建立Lua指令碼:/usr/local/lua/ip_block.lua
  2. 指令碼內容
    local redis = require "resty.redis"local red = redis:new()-- Redis連線引數local redis_host ="127.0.0.1"local redis_port =6379local redis_timeout =1000-- 毫秒local redis_auth =nil-- 無密碼留空-- 攔截引數local block_time =86400-- 封禁時間(1天)local time_window =60-- 統計視窗(1分鐘)local max_requests =60-- 最大請求數-- 獲取客戶端IPlocalfunctionget_client_ip()local headers = ngx.req.get_headers()return headers["X-Real-IP"]or headers["x_forwarded_for"]or ngx.var.remote_addrend-- 連線Redislocalfunctionconnect_redis()    red:set_timeout(redis_timeout)local ok, err = red:connect(redis_host, redis_port)ifnot ok then        ngx.log(ngx.ERR,"Redis連線失敗: ", err)returnnilendif redis_auth thenlocal ok, err = red:auth(redis_auth)ifnot ok then ngx.log(ngx.ERR,"Redis認證失敗: ", err)endendreturn okend-- 主邏輯local client_ip =get_client_ip()local counter_key ="limit:count:".. client_iplocal block_key ="limit:block:".. client_ip-- 檢查是否已封禁local is_blocked, err = red:get(block_key)iftonumber(is_blocked)==1then    ngx.exit(ngx.HTTP_FORBIDDEN)-- 直接返回403end-- 統計請求次數connect_redis()local current_count = red:incr(counter_key)if current_count ==1then    red:expire(counter_key, time_window)-- 首次設定過期時間end-- 觸發封禁條件if current_count > max_requests then    red:setex(block_key, block_time,1)-- 封禁並設定1天過期    red:del(counter_key)-- 刪除計數器    ngx.exit(ngx.HTTP_FORBIDDEN)end-- 釋放Redis連線red:set_keepalive(10000,100)

四、效能最佳化

  1. Redis連線池透過set_keepalive複用連線,避免頻繁建立TCP連線
  • 共享記憶體快取使用lua_shared_dict快取高頻訪問IP,減少Redis查詢壓力
  1. 非同步日誌記錄封禁操作非同步寫入日誌檔案,避免阻塞請求處理:
    ngx.timer.at(0,function()local log_msg = string.format("%s - IP %s blocked at %s",        ngx.var.host, client_ip, ngx.localtime())local log_file = io.open("/var/log/nginx/blocked_ips.log","a")    log_file:write(log_msg,"\n")    log_file:close()end)

五、驗證與測試

  1. 手動觸發封禁
    # 模擬高頻請求ab -100-c10 http://your-domain.com/# 檢查Redisredis-cli keys "limit:block:*"
  2. 自動解封驗證等待24小時後檢查封禁IP是否自動刪除:
    redis-cli ttl "limit:block:1.2.3.4"# 返回剩餘秒數

六、擴充套件方案

  1. 分散式封禁在多臺Nginx伺服器間共享Redis黑名單,實現叢集級攔截
    視覺化監控透過Grafana+Prometheus展示即時攔截資料:
    # 採集Redis指標prometheus-redis-exporter --redis.address=localhost:6379
    動態調整閾值透過Redis Hash儲存不同路徑的攔截規則:
    local rule_key="limit:rule:" .. ngx.var.urilocal custom_rule=red:hget(rule_key, "max_requests")

引用說明

  • 核心攔截邏輯參考了Nginx+Lua+Redis的經典架構設計
  • Redis鍵過期機制確保自動解封
  • 效能最佳化方案借鑑了OpenResty最佳實踐
END
想要學習Linux系統的讀者可以點選"閱讀原文"按鈕來了解書籍《Linux就該這麼學》,同時也非常適合專業的運維人員閱讀,成為輔助您工作的高價值工具書!

相關文章