基于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. 限连接数
05-07 21:21