鍍金池/ 教程/ Java/ 動(dòng)態(tài)限速
定時(shí)任務(wù)
函數(shù)的參數(shù)
超時(shí)
一個(gè) openresty 內(nèi)存“泄漏”問(wèn)題
獲取 uri 參數(shù)
局部變量
sleep
灰度發(fā)布
TIME_WAIT
代碼覆蓋率
連接池
CentOS 平臺(tái)安裝
稀疏數(shù)組
如何只啟動(dòng)一個(gè) timer 工作?
變量的共享范圍
break,return 關(guān)鍵字
Nginx
SQL 注入
如何引用第三方 resty 庫(kù)
不同階段共享變量
獲取請(qǐng)求 body
動(dòng)態(tài)生成的 lua-resty-redis 模塊方法
動(dòng)態(tài)加載證書(shū)和 OCSP stapling
repeat 控制結(jié)構(gòu)
編碼為 array 還是 object
Nginx 靜態(tài)文件服務(wù)
執(zhí)行階段概念
Lua 函數(shù)
日期時(shí)間函數(shù)
健康監(jiān)測(cè)
與其他 location 配合
for 控制結(jié)構(gòu)
函數(shù)定義
HTTPS 時(shí)代
點(diǎn)號(hào)與冒號(hào)操作符的區(qū)別
String 庫(kù)
文件操作
OpenResty 最佳實(shí)踐
<code>ngx.shared.DICT</code> 非隊(duì)列性質(zhì)
使用動(dòng)態(tài) DNS 來(lái)完成 HTTP 請(qǐng)求
代碼規(guī)范
什么是 JIT?
Windows 平臺(tái)安裝
正確的記錄日志
LuaNginxModule
不用標(biāo)準(zhǔn)庫(kù)
C10K 編程
控制結(jié)構(gòu)
請(qǐng)求中斷后的處理
Lua 環(huán)境搭建
Test::Nginx 能指定現(xiàn)成的 nginx.conf,而不是自動(dòng)生成一個(gè)嗎
Lua 基礎(chǔ)數(shù)據(jù)類(lèi)型
動(dòng)態(tài)限速
PostgresNginxModule
簡(jiǎn)單API Server框架
API 測(cè)試
location 匹配規(guī)則
虛變量
單元測(cè)試
防止 SQL 注入
select + set_keepalive 組合操作引起的數(shù)據(jù)讀寫(xiě)錯(cuò)誤
阻塞操作
全動(dòng)態(tài)函數(shù)調(diào)用
Web 服務(wù)
典型應(yīng)用場(chǎng)景
Nginx 新手起步
TLS session resumption
輸出響應(yīng)體
調(diào)用代碼前先定義函數(shù)
module 是邪惡的
怎樣理解 cosocket
模塊
Socket 編程發(fā)展
如何對(duì) Nginx Lua module 添加新 api
如何在后臺(tái)開(kāi)啟輕量級(jí)線程完成定時(shí)任務(wù)?
如何定位問(wèn)題
table 庫(kù)
json 解析的異常捕獲
如何安裝火焰圖生成工具
lua 中如何 continue
if 是邪惡的
為什么我們的域名不能被解析
抵制使用 module() 定義模塊
測(cè)試
body 在 location 中的傳遞
Lua 入門(mén)
子查詢
pipeline 壓縮請(qǐng)求數(shù)量
如何發(fā)起新 HTTP 請(qǐng)求
Lua 簡(jiǎn)介
緩存失效風(fēng)暴
Ubuntu 平臺(tái)安裝
日志輸出
緩存
Lua 面向?qū)ο缶幊?/span>
Nginx 陷阱和常見(jiàn)錯(cuò)誤
Redis 接口的二次封裝(發(fā)布訂閱)
日志
訪問(wèn)有授權(quán)驗(yàn)證的 Redis
正則表達(dá)式
lock
熱裝載代碼
調(diào)用 FFI 出現(xiàn) &quot;table overflow&quot;
數(shù)據(jù)合法性檢測(cè)
禁止某些終端訪問(wèn)
控制結(jié)構(gòu) if-else
調(diào)試
與 Docker 使用的網(wǎng)絡(luò)瓶頸
PostgresNginxModule 模塊的調(diào)用方式
用 do-end 整理你的代碼
FFI
什么時(shí)候使用
簡(jiǎn)介
環(huán)境搭建
Mac OS X 平臺(tái)安裝
火焰圖
負(fù)載均衡
while 型控制結(jié)構(gòu)
如何定位 openresty 崩潰 bug
使用 Nginx 內(nèi)置綁定變量
判斷數(shù)組大小
請(qǐng)求返回后繼續(xù)執(zhí)行
Redis 接口的二次封裝
KeepAlive
反向代理
協(xié)議無(wú)痛升級(jí)
數(shù)學(xué)庫(kù)
元表
Vanilla 介紹
HelloWorld
LuaCjsonLibrary
持續(xù)集成
代碼靜態(tài)分析
網(wǎng)上有大量對(duì) Lua 調(diào)優(yōu)的推薦,我們應(yīng)該如何看待?
script 壓縮復(fù)雜請(qǐng)求
非空判斷
性能測(cè)試
函數(shù)返回值
API 的設(shè)計(jì)
kong 介紹
表達(dá)式
不支持事務(wù)
LuaRestyDNSLibrary 簡(jiǎn)介

動(dòng)態(tài)限速

在應(yīng)用開(kāi)發(fā)中,經(jīng)常會(huì)有對(duì)請(qǐng)求進(jìn)行限速的需求。

通常意義上的限速,其實(shí)可以分為以下三種:

  1. limit_rate 限制響應(yīng)速度
  2. limit_conn 限制連接數(shù)
  3. limit_req 限制請(qǐng)求數(shù)

接下來(lái)讓我們看看,這三種限速在 OpenResty 中分別怎么實(shí)現(xiàn)。

限制響應(yīng)速度

Nginx 有一個(gè) $limit_rate,這個(gè)變量反映的是當(dāng)前請(qǐng)求每秒能響應(yīng)的字節(jié)數(shù)。該字節(jié)數(shù)默認(rèn)為配置文件中 limit_rate 指令的設(shè)值。 一如既往,通過(guò) OpenResty,我們可以直接在 Lua 代碼中動(dòng)態(tài)設(shè)置它。

access_by_lua_block {
    -- 設(shè)定當(dāng)前請(qǐng)求的響應(yīng)上限是 每秒 300K 字節(jié)
    ngx.var.limit_rate = "300K"
}

限制連接數(shù)和請(qǐng)求數(shù)

對(duì)于連接數(shù)和請(qǐng)求數(shù)的限制,我們可以求助于 OpenResty 官方的 lua-resty-limit-traffic 需要注意的是,lua-resty-limit-traffic 要求 OpenResty 版本在 1.11.2.2 以上(對(duì)應(yīng)的 lua-nginx-module 版本是 0.10.6)。 如果要配套更低版本的 OpenResty 使用,需要修改源碼。比如把代碼中涉及 incr(key, value, init) 方法,改成 incr(key, value)set(key, init) 兩步操作。這么改會(huì)增大有潛在 race condition 的區(qū)間。

lua-resty-limit-traffic 這個(gè)庫(kù)是作用于所有 Nginx worker 的。 由于數(shù)據(jù)同步上的局限,在限制請(qǐng)求數(shù)的過(guò)程中 lua-resty-limit-traffic 有一個(gè) race condition 的區(qū)間,可能多放過(guò)幾個(gè)請(qǐng)求。誤差大小取決于 Nginx worker 數(shù)量。 如果要求“寧可拖慢一千,不可放過(guò)一個(gè)”的精確度,恐怕就不能用這個(gè)庫(kù)了。你可能需要使用 lua-resty-lock 或外部的鎖服務(wù),只是性能上的代價(jià)會(huì)更高。

lua-resty-limit-traffic 的限速實(shí)現(xiàn)基于漏桶原理。 通俗地說(shuō),就是小學(xué)數(shù)學(xué)中,蓄水池一邊注水一邊放水的問(wèn)題。 這里注水的速度是新增請(qǐng)求/連接的速度,而放水的速度則是配置的限制速度。 當(dāng)注水速度快于放水速度(表現(xiàn)為池中出現(xiàn)蓄水),則返回一個(gè)數(shù)值 delay。調(diào)用者通過(guò) ngx.sleep(delay) 來(lái)減慢注水的速度。 當(dāng)蓄水池滿時(shí)(表現(xiàn)為當(dāng)前請(qǐng)求/連接數(shù)超過(guò)設(shè)置的 burst 值),則返回錯(cuò)誤信息 rejected。調(diào)用者需要丟掉溢出來(lái)的這部份。

下面是限制連接數(shù)的示例:

# nginx.conf
lua_code_cache on;
# 注意 limit_conn_store 的大小需要足夠放置限流所需的鍵值。
# 每個(gè) $binary_remote_addr 大小不會(huì)超過(guò) 16K,算上 lua_shared_dict 的節(jié)點(diǎn)大小,總共不到 64 字節(jié)。
# 100M 可以放 1.6M 個(gè)鍵值對(duì)
lua_shared_dict limit_conn_store 100M;
server {
    listen 8080;
    location / {
        access_by_lua_file src/access.lua;
        content_by_lua_file src/content.lua;
        log_by_lua_file src/log.lua;
    }
}
-- utils/limit_conn.lua
local limit_conn = require "resty.limit.conn"

-- new 的第四個(gè)參數(shù)用于估算每個(gè)請(qǐng)求會(huì)維持多長(zhǎng)時(shí)間,以便于應(yīng)用漏桶算法
local limit, limit_err = limit_conn.new("limit_conn_store", 10, 2, 0.05)
if not limit then
    error("failed to instantiate a resty.limit.conn object: ", limit_err)
end

local _M = {}

function _M.incoming()
    local key = ngx.var.binary_remote_addr
    local delay, err = limit: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

    if limit:is_committed() then
        local ctx = ngx.ctx
        ctx.limit_conn_key = key
        ctx.limit_conn_delay = delay
    end

    if delay >= 0.001 then
        ngx.log(ngx.WARN, "delaying conn, excess ", delay,
                "s per binary_remote_addr by limit_conn_store")
        ngx.sleep(delay)
    end
end

function _M.leaving()
    local ctx = ngx.ctx
    local key = ctx.limit_conn_key
    if key then
        local latency = tonumber(ngx.var.request_time) - ctx.limit_conn_delay
        local conn, err = limit:leaving(key, latency)
        if not conn then
            ngx.log(ngx.ERR,
            "failed to record the connection leaving ",
            "request: ", err)
        end
    end
end

return _M
-- src/access.lua
local limit_conn = require "utils.limit_conn"

-- 對(duì)于內(nèi)部重定向或子請(qǐng)求,不進(jìn)行限制。因?yàn)檫@些并不是真正對(duì)外的請(qǐng)求。
if ngx.req.is_internal() then
    return
end
limit_conn.incoming()
-- src/log.lua
local limit_conn = require "utils.limit_conn"

limit_conn.leaving()

注意在限制連接的代碼里面,我們用 ngx.ctx 來(lái)存儲(chǔ) limit_conn_key。這里有一個(gè)坑。內(nèi)部重定向(比如調(diào)用了 ngx.exec)會(huì)銷(xiāo)毀 ngx.ctx,導(dǎo)致 limit_conn:leaving() 無(wú)法正確調(diào)用。 如果需要限連業(yè)務(wù)里有用到 ngx.exec,可以考慮改用 ngx.var 而不是 ngx.ctx,或者另外設(shè)計(jì)一套存儲(chǔ)方式。只要能保證請(qǐng)求結(jié)束時(shí)能及時(shí)調(diào)用 limit:leaving() 即可。

限制請(qǐng)求數(shù)的實(shí)現(xiàn)差不多,這里就不贅述了。