ngx-lua實現高級限流方式一

基於POST請求體中的某個參數限流

背景

電商平台有活動,活動涉及優惠券的搶券,優惠券系統對大並發支援略差,為了保護整體系統平穩,因此在入口Nginx層對搶券介面做了一層限流。

完整實現如下:

lua_shared_dict my_limit_req_store 100m;
server {
    listen 80;
    server_name test.abc.com;

    # 搶券介面
    location = /api/v1/test {
        lua_need_request_body on;

        access_by_lua_block {
            local cjson = require("cjson")

            -- 獲取POST請求體
            if ngx.var.request_method == "POST" then
                ngx.req.read_body()

                local data = ngx.req.get_body_data()
                -- 獲取限流欄位 appName, 獲取不到則跳出lua
                if data then
                    params = cjson.decode(data)
                    if params["appName"] then
                        limit_key = params["appName"]
                    else
                        ngx.log(ngx.ERR, "未獲取到appName,不做限流")
                        -- 退出access_by_lua階段,繼續執行其他階段。
                        ngx.exit(0)
                    end
                else
                    ngx.log(ngx.ERR, "獲取請求體失敗,跳過限流配置")
                    ngx.exit(0)
                end
            else
                ngx.log(ngx.ERR, "不對非POST請求進行處理")
                ngx.exit(0)
            end

            -- 限流邏輯
            local limit_req = require "resty.limit.req"
            local lim, err = limit_req.new("my_limit_req_store", 200, 100)
            if not lim then
                    ngx.log(ngx.ERR, "failed to instantiate a resty.limit.req object: ", err)
                    return ngx.exit(500)
            end

            local delay, err = lim:incoming(limit_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 delay >= 0.001 then
                local excess = err
                ngx.sleep(delay)
            end
        }

        try_files $uri $uri/ /index.php?$query_string;
    }

    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }

    location  ~ \.php$ {
        # 設置項目根目錄
        set $PROJECT_NAME "/data/www/test.abc.com/public";
        # 設置upstream_name
        set $fastcgi_name test;

        index index.php index.html index.htm;
        include  fastcgi_params;
        fastcgi_pass   $fastcgi_name;
        fastcgi_index  index.php;
        fastcgi_param  SCRIPT_FILENAME  $PROJECT_NAME$fastcgi_script_name;     # 指定項目根目錄
    }
}

核心解讀

  1. 獲取請求體

    1. POST請求
    2. JSON格式
    3. 獲取不到不能影響其他階段繼續執行
  2. 解析請求體獲取目標參數

    1. cjson.decode()
    2. 操作map獲取目標數據(限流的指標)
    3. 獲取不到不能影響其他階段繼續執行
  3. 確定限流方式

    1. 限請求速率
    2. 限連接數
Tags: