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

【轉(zhuǎn)載請注明出處】:http://www.reibang.com/p/687e63118d84

限制接口總并發(fā)數(shù)

場景:
按照 ip 限制其并發(fā)連接數(shù)

lua_shared_dict my_limit_conn_store 100m;
...
location /hello {
    access_by_lua_block {
        local limit_conn = require "resty.limit.conn"
        -- 限制一個 ip 客戶端最大 1 個并發(fā)請求
        -- burst 設(shè)置為 0军熏,如果超過最大的并發(fā)請求數(shù)殿衰,則直接返回503歼狼,
        -- 如果此處要允許突增的并發(fā)數(shù)疹启,可以修改 burst 的值(漏桶的桶容量)
        -- 最后一個參數(shù)其實(shí)是你要預(yù)估這些并發(fā)(或者說單個請求)要處理多久,以便于對桶里面的請求應(yīng)用漏桶算法
        
        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 代表只是查看當(dāng)前請求要處理的延時情況和前面還未被處理的請求數(shù)
        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

        -- 如果請求連接計(jì)數(shù)等信息被加到shared dict中,則在ctx中記錄下蚁廓,
        -- 因?yàn)楹竺嬉嬷B接斷開雷激,以處理其他連接
        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
        -- 其實(shí)這里的 delay 肯定是上面說的并發(fā)處理時間的整數(shù)倍替蔬,
        -- 舉個例子,每秒處理100并發(fā)屎暇,桶容量200個承桥,當(dāng)時同時來500個并發(fā),則200個拒掉
        -- 100個在被處理根悼,然后200個進(jìn)入桶中暫存凶异,被暫存的這200個連接中,0-100個連接其實(shí)應(yīng)該延后0.5秒處理挤巡,
        -- 101-200個則應(yīng)該延后0.5*2=1秒處理(0.5是上面預(yù)估的并發(fā)處理時間)
        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
            -- 這個連接處理完后應(yīng)該告知一下剩彬,更新shared dict中的值,讓后續(xù)連接可以接入進(jìn)來處理
            -- 此處可以動態(tài)更新你之前的預(yù)估時間矿卑,但是別忘了把limit_conn.new這個方法抽出去寫喉恋,
            -- 要不每次請求進(jìn)來又會重置
            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;
}

說明:
其實(shí)此處沒有設(shè)置 burst 的值,就是單純的限制最大并發(fā)數(shù)母廷,如果設(shè)置了 burst 的值轻黑,并且做了延時處理,其實(shí)就是對并發(fā)數(shù)使用了漏桶算法琴昆,但是如果不做延時處理氓鄙,其實(shí)就是使用的令牌桶算法。參考下面對請求數(shù)使用漏桶令牌桶的部分椎咧,并發(fā)數(shù)的漏桶令牌桶實(shí)現(xiàn)與之相似

限制接口時間窗請求數(shù)

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

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)
        -- 如果請求數(shù)在限制范圍內(nèi)玖详,則當(dāng)前請求被處理的延遲(這種場景下始終為0,因?yàn)橐幢惶幚硪幢痪芙^)和將被處理的請求的剩余數(shù)
        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;
}
平滑限制接口請求數(shù)

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

lua_shared_dict my_limit_req_store 100m;
....

location /hello {
    access_by_lua_block {
        local limit_req = require "resty.limit.req"
        -- 這里設(shè)置rate=2/s蟋座,漏桶桶容量設(shè)置為0,(也就是來多少水就留多少水) 
        -- 因?yàn)閞esty.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 每分鐘只能調(diào)用 120 次 /hello 接口(平滑處理請求向臀,即每秒放過2個請求),超過部分進(jìn)入桶中等待诸狭,(桶容量為60)券膀,如果桶也滿了,則進(jìn)行限流

lua_shared_dict my_limit_req_store 100m;
....

location /hello {
    access_by_lua_block {
        local limit_req = require "resty.limit.req"
        -- 這里設(shè)置rate=2/s驯遇,漏桶桶容量設(shè)置為0芹彬,(也就是來多少水就留多少水) 
        -- 因?yàn)閞esty.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
        
        -- 此方法返回叉庐,當(dāng)前請求需要delay秒后才會被處理舒帮,和他前面對請求數(shù)
        -- 所以此處對桶中請求進(jìn)行延時處理,讓其排隊(duì)等待,就是應(yīng)用了漏桶算法
        -- 此處也是與令牌桶的主要區(qū)別既
        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;
}
令牌桶算法限流

令牌桶其實(shí)可以看著是漏桶的逆操作玩郊,看我們對把超過請求速率而進(jìn)入桶中的請求如何處理肢执,如果是我們把這部分請求放入到等待隊(duì)列中去,那么其實(shí)就是用了漏桶算法译红,但是如果我們允許直接處理這部分的突發(fā)請求预茄,其實(shí)就是使用了令牌桶算法。

場景:
限制 ip 每分鐘只能調(diào)用 120 次 /hello 接口(平滑處理請求侦厚,即每秒放過2個請求)耻陕,但是允許一定的突發(fā)流量(突發(fā)的流量,就是桶的容量(桶容量為60)假夺,超過桶容量直接拒絕

這邊只要將上面漏桶算法關(guān)于桶中請求的延時處理的代碼修改成直接送到后端服務(wù)就可以了淮蜈,這樣便是使用了令牌桶

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
        
        -- 此方法返回,當(dāng)前請求需要delay秒后才會被處理已卷,和他前面對請求數(shù)
        -- 此處忽略桶中請求所需要的延時處理梧田,讓其直接返送到后端服務(wù)器,
        -- 其實(shí)這就是允許桶中請求作為突發(fā)流量 也就是令牌桶桶的原理所在
        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;
}

說明:
其實(shí)nginx的ngx_http_limit_req_module 這個模塊中的delay和nodelay也就是類似此處對桶中請求是否做延遲處理的兩種方案侧蘸,也就是分別對應(yīng)的漏桶和令牌桶兩種算法

【轉(zhuǎn)載請注明出處】: http://www.reibang.com/p/687e63118d84

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末裁眯,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子讳癌,更是在濱河造成了極大的恐慌穿稳,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,042評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件晌坤,死亡現(xiàn)場離奇詭異逢艘,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)骤菠,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,996評論 2 384
  • 文/潘曉璐 我一進(jìn)店門它改,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人商乎,你說我怎么就攤上這事央拖。” “怎么了鹉戚?”我有些...
    開封第一講書人閱讀 156,674評論 0 345
  • 文/不壞的土叔 我叫張陵鲜戒,是天一觀的道長。 經(jīng)常有香客問我抹凳,道長遏餐,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,340評論 1 283
  • 正文 為了忘掉前任赢底,我火速辦了婚禮境输,結(jié)果婚禮上蔗牡,老公的妹妹穿的比我還像新娘颖系。我一直安慰自己嗅剖,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,404評論 5 384
  • 文/花漫 我一把揭開白布嘁扼。 她就那樣靜靜地躺著信粮,像睡著了一般。 火紅的嫁衣襯著肌膚如雪趁啸。 梳的紋絲不亂的頭發(fā)上强缘,一...
    開封第一講書人閱讀 49,749評論 1 289
  • 那天,我揣著相機(jī)與錄音不傅,去河邊找鬼旅掂。 笑死,一個胖子當(dāng)著我的面吹牛访娶,可吹牛的內(nèi)容都是我干的商虐。 我是一名探鬼主播,決...
    沈念sama閱讀 38,902評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼崖疤,長吁一口氣:“原來是場噩夢啊……” “哼秘车!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起劫哼,我...
    開封第一講書人閱讀 37,662評論 0 266
  • 序言:老撾萬榮一對情侶失蹤叮趴,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后权烧,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體眯亦,經(jīng)...
    沈念sama閱讀 44,110評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,451評論 2 325
  • 正文 我和宋清朗相戀三年般码,在試婚紗的時候發(fā)現(xiàn)自己被綠了妻率。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,577評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡侈询,死狀恐怖舌涨,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情扔字,我是刑警寧澤囊嘉,帶...
    沈念sama閱讀 34,258評論 4 328
  • 正文 年R本政府宣布,位于F島的核電站革为,受9級特大地震影響扭粱,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜震檩,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,848評論 3 312
  • 文/蒙蒙 一琢蛤、第九天 我趴在偏房一處隱蔽的房頂上張望蜓堕。 院中可真熱鬧,春花似錦博其、人聲如沸套才。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,726評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽背伴。三九已至,卻和暖如春峰髓,著一層夾襖步出監(jiān)牢的瞬間傻寂,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,952評論 1 264
  • 我被黑心中介騙來泰國打工携兵, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留疾掰,地道東北人。 一個月前我還...
    沈念sama閱讀 46,271評論 2 360
  • 正文 我出身青樓徐紧,卻偏偏與公主長得像静檬,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子浪汪,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,452評論 2 348