使用 Nginx+Lua 實現基于 IP 的訪問頻率限制
在高并發場景下,限制某個 IP 的訪問頻率是非常重要的,可以有效防止惡意攻擊或錯誤配置導致的服務宕機。以下是一個詳細的實現方案,使用 Nginx 和 Lua 腳本結合 Redis 來實現基于 IP 的訪問頻率限制。
1.環境準備
在開始之前,請確保已安裝以下組件:
? Nginx:作為反向代理服務器。
? Lua 模塊:需要在 Nginx 中編譯或安裝ngx_http_lua_module
。
? Redis:作為數據存儲,用于記錄訪問頻率。
2.方案概述
我們將通過 Lua 腳本和 Redis 實現以下功能:
? 記錄每個 IP 的訪問頻率。
? 設置閾值,當某個 IP 的訪問次數超過此閾值時,將其加入黑名單。
? 每次請求時檢查該 IP 是否在黑名單中。
3.Redis 數據結構
在 Redis 中,我們可以使用以下數據結構:
? 訪問計數存儲:ip:<client_ip>
,值為該 IP 的請求次數。
? 黑名單存儲:blocked_ip:<client_ip>
,值為 1(表示被封禁)。
4.Lua 腳本示例
以下是一個完整的 Lua 腳本示例,用于實現訪問頻率限制和封禁邏輯:
local redis = require "resty.redis"
local red = redis:new()
red:set_timeout(1000) -- 1秒超時-- 獲取客戶端 IP
local client_ip = ngx.var.remote_addr
local max_requests = 100 -- 設置最大請求次數
local expire_time = 60 -- 設置過期時間為60秒
local block_time = 300 -- 設置封禁時間為300秒-- 連接 Redis
local ok, err = red:connect("127.0.0.1", 6379)
if not ok thenngx.say("failed to connect to Redis: ", err)return
end-- 檢查是否被封禁
local blocked_res, err = red:get("blocked_ip:" .. client_ip)
if blocked_res == "1" thenngx.exit(ngx.HTTP_FORBIDDEN) -- 如果被封禁,返回403
end-- 記錄訪問次數
local count, err = red:incr("ip:" .. client_ip)
if not count thenngx.say("failed to increment IP counter: ", err)return
end-- 設置過期時間
if count == 1 thenred:expire("ip:" .. client_ip, expire_time)
end-- 檢查訪問次數是否超過閾值
if count > max_requests thenred:set("blocked_ip:" .. client_ip, 1)red:expire("blocked_ip:" .. client_ip, block_time) -- 封禁300秒ngx.exit(ngx.HTTP_FORBIDDEN) -- 返回403
end-- 關閉 Redis 連接
red:close()
5.Nginx 配置
接下來,我們需要在 Nginx 配置文件中使用此 Lua 腳本。
http {lua_shared_dict limit_dict 10m; # 定義共享內存字典server {listen 80;server_name example.com;location / {access_by_lua_block {-- 在此使用上述的 Lua 腳本local script = [[local redis = require "resty.redis"local red = redis:new()red:set_timeout(1000) -- 1秒超時-- 獲取客戶端 IPlocal client_ip = ngx.var.remote_addrlocal max_requests = 100 -- 設置最大請求次數local expire_time = 60 -- 設置過期時間為60秒local block_time = 300 -- 設置封禁時間為300秒-- 連接 Redislocal ok, err = red:connect("127.0.0.1", 6379)if not ok thenngx.say("failed to connect to Redis: ", err)returnend-- 檢查是否被封禁local blocked_res, err = red:get("blocked_ip:" .. client_ip)if blocked_res == "1" thenngx.exit(ngx.HTTP_FORBIDDEN) -- 如果被封禁,返回403end-- 記錄訪問次數local count, err = red:incr("ip:" .. client_ip)if not count thenngx.say("failed to increment IP counter: ", err)returnend-- 設置過期時間if count == 1 thenred:expire("ip:" .. client_ip, expire_time)end-- 檢查訪問次數是否超過閾值if count > max_requests thenred:set("blocked_ip:" .. client_ip, 1)red:expire("blocked_ip:" .. client_ip, block_time) -- 封禁300秒ngx.exit(ngx.HTTP_FORBIDDEN) -- 返回403end-- 關閉 Redis 連接red:close()]]ngx.execute_lua(script)}proxy_pass http://backend;}}
}
6.測試
? 啟動 Redis:
redis-server
? 啟動 Nginx:
nginx -s reload
? 訪問測試:
? 使用瀏覽器或工具(如curl
)訪問你的 Nginx 服務。
? 在 60 秒內多次訪問,觀察是否觸發封禁邏輯。
7.注意事項
? 連接池:
? 在生產環境中,建議使用連接池來管理 Redis 連接,避免頻繁建立和關閉連接。
? 錯誤處理:
? 在 Lua 腳本中,確保對 Redis 連接失敗等錯誤進行適當的處理,避免影響正常服務。
? 性能優化:
? 根據實際需求調整max_requests
、expire_time
和block_time
等參數,以達到最佳性能。
總結
通過結合 Nginx、Lua 和 Redis,我們實現了一個簡單而有效的基于 IP 的訪問頻率限制機制。這種方案不僅提高了服務器的安全性,還能有效防止惡意攻擊。在實際應用中,你可以根據具體的場景和需求調整訪問頻率的限制和封禁策略。希望這些方法和示例對你有所幫助!