使用nginx+lua,對現有系統進行限流降級,保證系統穩定性

背景:當前對外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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章