背景:当前对外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