背景:当前对外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包,参考:
https://blog.csdn.net/boshuzhang/article/details/75258408
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/pintsized/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