接入層限流之OpenResty提供的Lua限流模塊lua-resty-limit-traffic

【轉載請註明出處】:http://www.javashuo.com/article/p-gcglgvro-kw.htmlnode

限制接口總併發數

場景:
按照 ip 限制其併發鏈接數nginx

lua_shared_dict my_limit_conn_store 100m;
...
location /hello {
    access_by_lua_block {
        local limit_conn = require "resty.limit.conn"
        -- 限制一個 ip 客戶端最大 1 個併發請求
        -- burst 設置爲 0,若是超過最大的併發請求數,則直接返回503,
        -- 若是此處要容許突增的併發數,能夠修改 burst 的值(漏桶的桶容量)
        -- 最後一個參數實際上是你要預估這些併發(或者說單個請求)要處理多久,以便於對桶裏面的請求應用漏桶算法
        
        local lim, err = limit_conn.new("my_limit_conn_store", 1, 0, 0.5)              
        if not lim then
            ngx.log(ngx.ERR, "failed to instantiate a resty.limit.conn object: ", err)
            return ngx.exit(500)
        end

        local key = ngx.var.binary_remote_addr
        -- commit 爲true 表明要更新shared dict中key的值,
        -- false 表明只是查看當前請求要處理的延時狀況和前面還未被處理的請求數
        local delay, err = lim:incoming(key, true)
        if not delay then
            if err == "rejected" then
                return ngx.exit(503)
            end
            ngx.log(ngx.ERR, "failed to limit req: ", err)
            return ngx.exit(500)
        end

        -- 若是請求鏈接計數等信息被加到shared dict中,則在ctx中記錄下,
        -- 由於後面要告知鏈接斷開,以處理其餘鏈接
        if lim:is_committed() then
            local ctx = ngx.ctx
            ctx.limit_conn = lim
            ctx.limit_conn_key = key
            ctx.limit_conn_delay = delay
        end

        local conn = err
        -- 其實這裏的 delay 確定是上面說的併發處理時間的整數倍,
        -- 舉個例子,每秒處理100併發,桶容量200個,當時同時來500個併發,則200個拒掉
        -- 100個在被處理,而後200個進入桶中暫存,被暫存的這200個鏈接中,0-100個鏈接其實應該延後0.5秒處理,
        -- 101-200個則應該延後0.5*2=1秒處理(0.5是上面預估的併發處理時間)
        if delay >= 0.001 then
            ngx.sleep(delay)
        end
    }

    log_by_lua_block {
        local ctx = ngx.ctx
        local lim = ctx.limit_conn
        if lim then
            local key = ctx.limit_conn_key
            -- 這個鏈接處理完後應該告知一下,更新shared dict中的值,讓後續鏈接能夠接入進來處理
            -- 此處能夠動態更新你以前的預估時間,可是別忘了把limit_conn.new這個方法抽出去寫,
            -- 要不每次請求進來又會重置
            local conn, err = lim:leaving(key, 0.5)
            if not conn then
                ngx.log(ngx.ERR,
                        "failed to record the connection leaving ",
                        "request: ", err)
                return
            end
        end
    }
    proxy_pass http://10.100.157.198:6112;
    proxy_set_header Host $host;
    proxy_redirect off;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_connect_timeout 60;
    proxy_read_timeout 600;
    proxy_send_timeout 600;
}

說明:
其實此處沒有設置 burst 的值,就是單純的限制最大併發數,若是設置了 burst 的值,而且作了延時處理,其實就是對併發數使用了漏桶算法,可是若是不作延時處理,其實就是使用的令牌桶算法。參考下面對請求數使用漏桶令牌桶的部分,併發數的漏桶令牌桶實現與之類似算法

限制接口時間窗請求數

場景:
限制 ip 每分鐘只能調用 120 次 /hello 接口(容許在時間段開始的時候一次性放過120個請求)segmentfault

lua_shared_dict my_limit_count_store 100m;
...

init_by_lua_block {
    require "resty.core"
}
....

location /hello {
    access_by_lua_block {
        local limit_count = require "resty.limit.count"

        -- rate: 10/min 
        local lim, err = limit_count.new("my_limit_count_store", 120, 60)
        if not lim then
            ngx.log(ngx.ERR, "failed to instantiate a resty.limit.count object: ", err)
            return ngx.exit(500)
        end

        local key = ngx.var.binary_remote_addr
        local delay, err = lim:incoming(key, true)
        -- 若是請求數在限制範圍內,則當前請求被處理的延遲(這種場景下始終爲0,由於要麼被處理要麼被拒絕)和將被處理的請求的剩餘數
        if not delay then
            if err == "rejected" then
                return ngx.exit(503)
            end

            ngx.log(ngx.ERR, "failed to limit count: ", err)
            return ngx.exit(500)
        end
    }

    proxy_pass http://10.100.157.198:6112;
    proxy_set_header Host $host;
    proxy_redirect off;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_connect_timeout 60;
    proxy_read_timeout 600;
    proxy_send_timeout 600;
}
平滑限制接口請求數

場景:
限制 ip 每分鐘只能調用 120 次 /hello 接口(平滑處理請求,即每秒放過2個請求)後端

lua_shared_dict my_limit_req_store 100m;
....

location /hello {
    access_by_lua_block {
        local limit_req = require "resty.limit.req"
        -- 這裏設置rate=2/s,漏桶桶容量設置爲0,(也就是來多少水就留多少水) 
        -- 由於resty.limit.req代碼中控制粒度爲毫秒級別,因此能夠作到毫秒級別的平滑處理
        local lim, err = limit_req.new("my_limit_req_store", 2, 0)
        if not lim then
            ngx.log(ngx.ERR, "failed to instantiate a resty.limit.req object: ", err)
            return ngx.exit(500)
        end

        local key = ngx.var.binary_remote_addr
        local delay, err = lim:incoming(key, true)
        if not delay then
            if err == "rejected" then
                return ngx.exit(503)
            end
            ngx.log(ngx.ERR, "failed to limit req: ", err)
            return ngx.exit(500)
        end
    }

    proxy_pass http://10.100.157.198:6112;
    proxy_set_header Host $host;
    proxy_redirect off;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_connect_timeout 60;
    proxy_read_timeout 600;
    proxy_send_timeout 600;
}
漏桶算法限流

場景:
限制 ip 每分鐘只能調用 120 次 /hello 接口(平滑處理請求,即每秒放過2個請求),超過部分進入桶中等待,(桶容量爲60),若是桶也滿了,則進行限流服務器

lua_shared_dict my_limit_req_store 100m;
....

location /hello {
    access_by_lua_block {
        local limit_req = require "resty.limit.req"
        -- 這裏設置rate=2/s,漏桶桶容量設置爲0,(也就是來多少水就留多少水) 
        -- 由於resty.limit.req代碼中控制粒度爲毫秒級別,因此能夠作到毫秒級別的平滑處理
        local lim, err = limit_req.new("my_limit_req_store", 2, 60)
        if not lim then
            ngx.log(ngx.ERR, "failed to instantiate a resty.limit.req object: ", err)
            return ngx.exit(500)
        end

        local key = ngx.var.binary_remote_addr
        local delay, err = lim:incoming(key, true)
        if not delay then
            if err == "rejected" then
                return ngx.exit(503)
            end
            ngx.log(ngx.ERR, "failed to limit req: ", err)
            return ngx.exit(500)
        end
        
        -- 此方法返回,當前請求須要delay秒後纔會被處理,和他前面對請求數
        -- 因此此處對桶中請求進行延時處理,讓其排隊等待,就是應用了漏桶算法
        -- 此處也是與令牌桶的主要區別既
        if delay >= 0.001 then
            ngx.sleep(delay)
        end
    }

    proxy_pass http://10.100.157.198:6112;
    proxy_set_header Host $host;
    proxy_redirect off;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_connect_timeout 60;
    proxy_read_timeout 600;
    proxy_send_timeout 600;
}
令牌桶算法限流

令牌桶其實能夠看着是漏桶的逆操做,看咱們對把超過請求速率而進入桶中的請求如何處理,若是是咱們把這部分請求放入到等待隊列中去,那麼其實就是用了漏桶算法,可是若是咱們容許直接處理這部分的突發請求,其實就是使用了令牌桶算法。併發

場景:
限制 ip 每分鐘只能調用 120 次 /hello 接口(平滑處理請求,即每秒放過2個請求),可是容許必定的突發流量(突發的流量,就是桶的容量(桶容量爲60),超過桶容量直接拒絕ui

這邊只要將上面漏桶算法關於桶中請求的延時處理的代碼修改爲直接送到後端服務就能夠了,這樣即是使用了令牌桶lua

lua_shared_dict my_limit_req_store 100m;
....

location /hello {
    access_by_lua_block {
        local limit_req = require "resty.limit.req"

        local lim, err = limit_req.new("my_limit_req_store", 2, 0)
        if not lim then
            ngx.log(ngx.ERR, "failed to instantiate a resty.limit.req object: ", err)
            return ngx.exit(500)
        end

        local key = ngx.var.binary_remote_addr
        local delay, err = lim:incoming(key, true)
        if not delay then
            if err == "rejected" then
                return ngx.exit(503)
            end
            ngx.log(ngx.ERR, "failed to limit req: ", err)
            return ngx.exit(500)
        end
        
        -- 此方法返回,當前請求須要delay秒後纔會被處理,和他前面對請求數
        -- 此處忽略桶中請求所須要的延時處理,讓其直接返送到後端服務器,
        -- 其實這就是容許桶中請求做爲突發流量 也就是令牌桶桶的原理所在
        if delay >= 0.001 then
        --    ngx.sleep(delay)
        end
    }

    proxy_pass http://10.100.157.198:6112;
    proxy_set_header Host $host;
    proxy_redirect off;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_connect_timeout 60;
    proxy_read_timeout 600;
    proxy_send_timeout 600;
}

說明:
其實nginx的ngx_http_limit_req_module 這個模塊中的delay和nodelay也就是相似此處對桶中請求是否作延遲處理的兩種方案,也就是分別對應的漏桶和令牌桶兩種算法spa

【轉載請註明出處】: http://www.javashuo.com/article/p-gcglgvro-kw.html

相關文章
相關標籤/搜索