背景:当前对外api服务的使用者日趋增长,现有系统服务能力有限,需要做对其做容量规划,防止外界系统对当前系统的过渡调用,导致服务超载,影响核心业务的使用,故需对服务做限流措施,了解了几种限流方案,最终选择nginx+lua来实现,对现有系统无侵入,话不多说,切入正题!
1、现有linux系统nginx版本:tengine 2.2.2 服务端:java ,需先对nginx升级以支持lua
升级步骤:
1)下载安装LuaJIT 2.1(推荐最新)
cd /usr/local/src wget http://luajit.org/download/LuaJIT-2.1.0-beta2.tar.gz tar zxf LuaJIT-2.1.0-beta2.tar.gz cd LuaJIT-2.1.0-beta2 make PREFIX=/usr/local/luajit make install PREFIX=/usr/local/luajit
2)下载ngx_devel_kit(NDK)模块(推荐最新)
cd /usr/local/src wget https://github.com/simplresty/ngx_devel_kit/archive/v0.3.1rc1.tar.gzr tar -xzvf ngx_devel_kit-0.3.1rc1.tar.gz
3)下载最新的lua-nginx-module 模块(推荐最新)
cd /usr/local/src wget https://github.com/openresty/lua-nginx-module/archive/v0.10.13.tar.gz tar -xzvf v0.10.2.tar.gz
4)设置环境变量vim /etc/profile 加入:
export LUAJIT_LIB=/usr/local/luajit/lib export LUAJIT_INC=/usr/local/luajit/include/luajit-2.1 source /etc/profile
5)nginx -V查看已经编译的配置 ./configure --prefix=/usr/local/nginx
在原有配置基础上加入以下模块:(注意路径)
--add-module=/usr/local/src/ngx_devel_kit-0.3.1rc1 --add-module=/usr/local/src/lua-nginx-module-0.10.2
a、进入tengine解压目录重新编译
./configure --prefix=/usr/local/nginx --add-module=/usr/local/src/ngx_devel_kit-0.3.1rc1 --add-module=/usr/local/src/lua-nginx-module-0.10.13
b、安装
make -j2 make install
6)重启nginx,报错
/usr/local/nginx/sbin/nginx: error while loading shared libraries: libluajit-5.1.so.2: cannot open shared object file: No such file or directory
解决方法:ln -s /usr/local/luajit/lib/libluajit-5.1.so.2 /usr/local/lib/libluajit-5.1.so.2
查看 : cat /etc/ld.so.conf
是否包含此内容:include ld.so.conf.d/*.conf 若不包含:执行
echo “include ld.so.conf.d/*.conf” >> /etc/ld.so.conf echo “/usr/local/lib” >> /etc/ld.so.conf ldconfig
7)重启nginx,使新模块生效
/usr/local/nginx/sbin/nginx -s stop /usr/local/nginx/sbin/nginx -s start
8)lua增加cjson包,参考:
9)引入lua-resty-limit-traffic-master 库
https://github.com/openresty/lua-resty-limit-traffic/archive/master.zip
将lib下的包放入/usr/local/nginx目录,并在http块配置:
lua_package_path "/usr/local/nginx/resty/limit/?.lua;;";
10)引入http 解压之后将 lib 下的两个lua文件放在/usr/local/nginx/resty/,参考:
https://github.com/ledgetech/lua-resty-http
致此,lua环境已安装完成,验证即可
2、开发自己的lua代码,根据openresty官方给出的限流示例,稍加修改即可,如下
--- --- Generated by EmmyLua(https://github.com/EmmyLua) --- Created by Ning_MX. --- DateTime: 2018/7/30 10:54 --- local _M = { _VERSION = '1.0.0' } local limit_req = require "resty.limit.req" local common_util = require 'common_util' local new_timer = ngx.timer.at local http = require "http" local cjson = require 'cjson' local notify = require 'notify' local pcall = common_util.lib_func_xpcall local rejected_code = 513 local url_get_rules = "http:/*********" -- 存放规则的共享缓存 local shd_rules_dict = ngx.shared.limit_rules local my_limit_req_store = "my_limit_req_store" --[[ @info: uri限速方法 rate:触发限速的请求数 burst:触发限速后扔可访问的请求数 uri:请求uri,限速维度 nginx异常状态码 513(超出rate+burst的请求) --]] local function limit_uri() local uri = ngx.var.uri local value = shd_rules_dict:get(uri) --未配置限速的URI,结束程序 if common_util.isnil(value) then --common_util.dbg_err("no rate limit, the uri : "..uri) return end local data = cjson.decode(value) local db_status = data["status"] --状态未开启限速的URI,结束程序 if type(db_status) ~= "nil" and tonumber(db_status) == 2 then --common_util.dbg_err("rate limit not open the uri : "..uri) return end local db_nginxCount = data["nginxCount"] local db_rate = data["rate"] local db_appCount = data["appCount"] local db_notifyGroup = data["notifyGroup"] local rate = (db_appCount * db_rate * 0.7) /db_nginxCount local burst = (db_appCount * db_rate /db_nginxCount) - rate local lim, err = limit_req.new(my_limit_req_store, rate,burst) if not lim then common_util.dbg_err("failed to instantiate a resty.limit.req object : "..err) return end local delay, err = lim:incoming(uri, true) -- 触发限速逻辑 if not delay then if err == "rejected" then -- 发送报警 notify.sendOnePiece(db_notifyGroup,"trigger the burst rate limit ,error info :" .. uri.." remote_ip:"..ngx.var.remote_addr ) common_util.dbg_err("trigger the burst rate limit, the uri : "..uri .." error info :"..err) return ngx.exit(rejected_code) else common_util.dbg_err("failed to instantiate a resty.limit.req object : "..err) end end -- 触发限速,若限速时间大于10ms,nginx休眠 if delay >= 0.001 then common_util.dbg_err("the request will delay time "..delay .. " the uri : "..uri ) ngx.sleep(delay) else -- common_util.dbg_err(" normal req: ".. delay) -- 正常速率之内 end end -- 生成定时程序,循环执行 local function timer_routine_boot(premature, timer_span, routine_func, ...) if premature then return end local flag, info = pcall(routine_func, ...) if flag == false then common_util.log_error("timer routine exception:" .. tostring(info)) end local ok, err = new_timer(timer_span, timer_routine_boot, timer_span, routine_func, ...) if not ok then local b = ngx.worker.exiting() if common_util.typeb(b) and b == true then else common_util.log_error("set timer failed!!!" .. "do not apply this worker's timer anymore:" .. tostring(err)) end end end local function timer_init(timer_span, routine_func, ...) common_util.atypen(timer_span) if timer_span == 0 then timer_span = 1 end local delay = 0 local ok, err = new_timer(delay, timer_routine_boot, timer_span, routine_func, ...) if not ok then local b = ngx.worker.exiting() if common_util.typeb(b) and b == true then else common_util.log_error("req_rate_limit:failed to create timer:" .. tostring(err)) end return end end --定时加载配置uri信息,通过调接口实现 local function timer_load_rules() common_util.dbg_err("timer_load_rules start ") local httpc = http.new() httpc:set_timeout(10000) local res, err = httpc:request_uri(url_get_rules) if common_util.notnil(err) then common_util.dbg_err("query err " .. tostring(err)) return nil end if res and res.status == 200 then local body = res.body httpc:set_keepalive(10000, 100) if common_util.notnil(body) then local data = cjson.decode(body) common_util.dbg_err("url get rules body : " .. tostring(body) .. " data size :" .. #data) for i=1 ,#data do local uri_key = data[i]["uri"] local uri_val= cjson.encode(data[i]) --把每条规则放入共享内存 key:uri value: 规则信息 shd_rules_dict:safe_set(uri_key,uri_val) end end else common_util.dbg_err("url get error res body : " .. tostring(body)) end end function _M.timer_worker() timer_init(60, timer_load_rules) end -- 请求限速入口 function _M.access_limit() local status, result = pcall(limit_uri) if status == false or common_util.notnil(result) then common_util.dbg_err("limit req fail :" .. result) end end return _M
对以上程序做简要说明:使用定时器定时加载保存在myslq中的所要限速的uri规则(并不是所有uri都需要限速) ,common_util是自定义的一个工具类,封装了ngx日志工具等,notify是自定义的报警程序,可根据自己的报警系统做定制
3、nginx配置及程序接入
1)、部署代码到指定nginx服务器(需支持ngx-lua模块,并引入相关lib)的nginx下的mylua目录
2)、修改nginx.conf配置文件
http块加入:
lua_package_path "/usr/local/nginx/mylua/?.lua;/usr/local/nginx/resty/?.lua;/usr/loc al/nginx/resty/limit/?.lua;;"; lua_shared_dict my_limit_req_store 100m; lua_shared_dict limit_rules 10m; init_worker_by_lua ' local alr = require "access_limit_req" alr.timer_worker() ';
在需要限速的server块加入:
access_by_lua ' local alr = require "access_limit_req" alr.access_limit() ';
3)、在mysql中配置相关uri规则,请求不区分请求类型,只和uri有关,触发限速后nginx直接返回513