openresty是以ngx_lua模塊為核心的nginx套件,詳見春哥項目 http://openresty.org/ 。
nginx充分利用epoll,擅長處理高并發(fā);而lua作為天生的膠水語言,開發(fā)簡單。兩者結(jié)合起來,可以很容易實現(xiàn)以前PHP幾乎不能完成的應(yīng)用。
最近在某游戲激活碼搶號專題中,有個場景并發(fā)較高,可慮采用lua做PHP應(yīng)用層的防火墻。
搶號專題,前期評估預(yù)計2萬人參加,并發(fā)峰值按20k來扛,網(wǎng)頁前端采用隨機延時發(fā)請求來減輕負載,實際產(chǎn)生的并發(fā)連接大概在0.5k。
架構(gòu)方面,兩臺webserver + 一臺mysql,32核,36G內(nèi)存。其中一臺webserver為主,起有memcache作為分布式session以及data cache,redis做隊列,使用nginx反代做簡單的負載均衡。內(nèi)核參數(shù)皆已調(diào)優(yōu)。
根據(jù)各種參數(shù)組合的基準壓測,發(fā)現(xiàn)fpm響應(yīng)在1ms,單核RPS為1K左右,32核可以跑到30K+,這是我實測見過的最高PHP 單機基準RPS了。但是nginx最高卻只跑到25k,多方求解,至今無果。
總得來說,我還是相信nginx比fpm更加健壯以及更少的資源占用。 下面是我們在nginx層面的lua實踐。
1 2 3 4 5 6 7 8 9 10 11 12 | vim /usr/local/openresty/<a href= "http://chuyinfeng.com/tag/nginx" class = "st_tag internal_tag" rel= "tag" title= "標簽 Nginx 下的日志" >nginx</a>/conf/<a href= "http://chuyinfeng.com/tag/nginx" class = "st_tag internal_tag" rel= "tag" title= "標簽 Nginx 下的日志" >nginx</a>.conf http { # access dict,初始化使用到的共享內(nèi)存 <a href= "http://chuyinfeng.com/tag/lua" class = "st_tag internal_tag" rel= "tag" title= "標簽 Lua 下的日志" >lua</a>_shared_dict access_whitelist 1m; <a href= "http://chuyinfeng.com/tag/lua" class = "st_tag internal_tag" rel= "tag" title= "標簽 Lua 下的日志" >lua</a>_shared_dict access_blacklist 1m; <a href= "http://chuyinfeng.com/tag/lua" class = "st_tag internal_tag" rel= "tag" title= "標簽 Lua 下的日志" >lua</a>_shared_dict access_iplist 40m; <a href= "http://chuyinfeng.com/tag/lua" class = "st_tag internal_tag" rel= "tag" title= "標簽 Lua 下的日志" >lua</a>_shared_dict access_total 1m; # access 訪問控制lua腳本 access_by_<a href= "http://chuyinfeng.com/tag/lua" class = "st_tag internal_tag" rel= "tag" title= "標簽 Lua 下的日志" >lua</a>_file /usr/local/openresty/lualib/com/access.<a href= "http://chuyinfeng.com/tag/lua" class = "st_tag internal_tag" rel= "tag" title= "標簽 Lua 下的日志" >lua</a>; |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 | vim /usr/local/openresty/lualib/com/access.lua --[[ -- access.lua, 訪問控制 -- @author 楚吟風(fēng) <chuyinfeng@gmail.com> -- @version 1.0 -- @update 2013-04-25 -- @package com ]] -- 靜態(tài)白名單,從文件加載 local whitelist = ngx.shared.access_whitelist -- 靜態(tài)黑名單,從共享內(nèi)存載入 local blacklist = ngx.shared.access_blacklist -- IP計數(shù)器,從共享內(nèi)存載入 local iplist = ngx.shared.access_iplist -- 全局計數(shù)器,從共享內(nèi)存再入 local total = ngx.shared.access_total -- 獲取客戶端IP local ip = ngx. var .remote_addr -- 單個IP RPS限制 local ip_rps = 50 -- 單個IP RPS 系統(tǒng)防火墻攔截標準 local iptable_rps = 100 -- 總RPS限制 local total_rps = 3000 -- 重新載入名單 local ctrl_path = '/access_ctrl/reload/' -- 防火墻靜態(tài)黑白名單存放路徑 local file_path = '/usr/local/openresty/lualib/com/access/' -- ip認證 local auth = { [ '/admin/' ] = { '192.168.20.15' } } -- ip 認證 local is_banned = false local uri = string.lower(ngx. var .request_uri) for path, iplist in pairs(auth) do local i, _ = string.find(uri, path) if i == 1 then is_banned = true for _, cip in pairs(iplist) do if string.find(ip, cip) then is_banned = false end end end end if is_banned then --ngx.header[ 'Content-Type' ] = 'text/plain' --ngx.say(ip .. ' is banned' ) --ngx. exit (ngx.HTTP_OK); end -- 從文件載入字典 function load_file_to_dict(file, dict) dict:flush_all() local fh = io.open(file, 'r' ) for line in fh:lines() do dict:set(line, '' ) end fh:close() end -- 載入靜態(tài)名單 if (whitelist:get( '0.0.0.0' ) == nil) or (ngx. var .request_uri == ctrl_path) then load_file_to_dict(file_path .. 'whitelist.txt' , whitelist) load_file_to_dict(file_path .. 'blacklist.txt' , blacklist) end --[[ -- is_block, 檢測當(dāng)前IP是否被屏蔽 ]] function is_block() -- 如果在靜態(tài)白名單,則直接放行 if whitelist:get(ip) then return false end -- 如果在靜態(tài)黑名單,則攔截 if blacklist:get(ip) then return { [ 'status' ] = 1, [ 'tips' ] = (ip .. ' is in blacklist, please contact us' ), } end -- 當(dāng)前IP請求次數(shù)加1 local ip_times = iplist:incr(ip, 1) -- 如果訪問記錄為空,則設(shè)置訪問次數(shù)為1 if ip_times == nil then ip_times = 1 iplist:set(ip, 1, 1) end -- 如果請求頻率超過單個IP系統(tǒng)防火墻限制,則寫入防火墻名單 if ip_times == iptable_rps then local file = io.open(file_path .. 'blocklist.txt' , 'a' ) file:write( "\r\n" .. ip) file:close() ngx.say( 'will in iptables' );ngx. exit (ngx.HTTP_OK) end -- 如果請求頻率超過單個IP限制則封禁,超過多少個封禁多少秒 if ip_times > ip_rps then local sec = (ip_times - ip_rps) iplist:set(ip, ip_times, sec) return { [ 'status' ] = 2, [ 'tips' ] = ip .. ' is blocked for ' .. sec .. ' seconds.' , [ 'sec' ] = sec, } end -- 全局請求次數(shù)加1 local total_times = total:incr( 'total' , 1) if total_times == nil then total_times = 1 total:set( 'total' , 1, 1) end -- 全局請求攔截 if total_times > total_rps then return { [ 'status' ] = 3, [ 'tips' ] = total_times .. ' is request, please wait for a moment' , [ 'total' ] = total_times, } end end -- 主函數(shù) function main() local block = is_block() if block then ngx.req.set_header( "Content-Type" , "text/plain" ) --ngx.say(block[ 'status' ]) ngx.say(block[ 'tips' ]) ngx. exit (ngx.HTTP_OK) end end main() |
壓測結(jié)果顯示,采用ngx_lua,與原生nginx性能幾無差別,應(yīng)用層防護效果非常顯著。