OpenResty单机限流


前言: 上一篇文章介绍了OpenResty的安装和lua的基本语法学习,这一篇介绍一下使用OpenResty进行限流。本文只对限流功能实现做简单介绍,不对限流算法原理做过多解释。

该项目已经实现的限流方法

  • 固定窗口(限制单位时间内接口可以请求的数量)
  • 令牌桶
  • 计数器(限制接口并发连接数)

限流流程图

限流规则存储

在这里插入图片描述

针对接口限流

在这里插入图片描述

限流算法实现主要代码部分

固定窗口

time_range_request_count(rule_detail)
    local request_uri = ngx.var.uri
    local limitTimeRange = rule_detail.limitTimeRange
    local limitNum = rule_detail.limitNum
    if not limitTimeRange or not limitNum then
        ngx.log(ngx.ERR, "limitTimeRange or limitNum is nil")
        return
    end
    -- 检查 limitTimeRange 和 limitNum 的类型
    if type(limitTimeRange) ~= "number" or type(limitNum) ~= "number" then
        ngx.log(ngx.ERR, "limitTimeRange 或 limitNum 的类型不是数字","rule_detail: "..cjson.encode(rule_detail))
        return
    end

    local lim, err = limit_count.new("api_rate_limit_req_store", limitNum, limitTimeRange)
    if not lim then
        ngx.log(ngx.ERR, "创建限流器失败 err: ", err.." rule_detail: "..cjson.encode(rule_detail).." uri: "..request_uri)
        --return ngx.exit(500)
        baseUtil.api_rate_limit_res(500,500,"服务器异常")
    end
    local delay, response = lim:incoming(request_uri, true)
    if not delay then
        if response == "rejected" then
            ngx.log(ngx.ERR, "限制请求数 rejected: "," rule_detail: "..cjson.encode(rule_detail).." uri: "..request_uri)
            --return ngx.exit(429)
            baseUtil.api_rate_limit_res(429,429,"服务器异常")
        end
        ngx.log(ngx.ERR, "failed to limit count: ", tostring(response).." rule_detail: "..cjson.encode(rule_detail).." uri: "..request_uri)
        --return ngx.exit(500)
        baseUtil.api_rate_limit_res(500,500,"服务器异常")
    end
end

return {
    time_range_request_count = time_range_request_count
}

令牌桶

local limit_req = require "resty.limit.req"
local cjson = require "cjson"
local baseUtil = require "BaseUtil"

-- 令牌桶算法限流
local function token_bucket_algorithm(rule_detail)
    local request_uri = ngx.var.uri
    -- 限流速率 r/s
    local rate = rule_detail.rate
    -- 令牌数量
    local burst = rule_detail.burst
    -- 检查 rate 和 burst 的类型
    if type(rate) ~= "number" or type(burst) ~= "number" then
        ngx.log(ngx.ERR, "rate 或 burst 的类型不是数字","rule_detail: "..cjson.encode(rule_detail))
        return
    end
    local lim, err = limit_req.new("api_rate_limit_req_store", rate, burst)
    if not lim then
        ngx.log(ngx.ERR, "令牌桶限流,创建限流器失败", err)
        --return ngx.exit(500)
        baseUtil.api_rate_limit_res(500,500,"服务器异常")
    end
    -- 调用限流器
    local delay, response = lim:incoming(request_uri, true)
    if not delay then
        if response == "rejected" then
            ngx.log(ngx.ERR, "限制请求数 rejected: "," rule_detail: "..cjson.encode(rule_detail).." uri: "..request_uri)
            --return ngx.exit(429)  -- 返回 HTTP 429 状态码
            baseUtil.api_rate_limit_res(429,429,"服务器异常")
        end
        ngx.log(ngx.ERR, "调用限流器失败: ", tostring(response).." rule_detail: "..cjson.encode(rule_detail).." uri: "..request_uri)
        --return ngx.exit(500)
        baseUtil.api_rate_limit_res(500,500,"服务器异常")
    end
    -- 如果请求没有超过限制,正常处理请求
    if delay >= 0 then
        ngx.sleep(delay)
    end
end

return {
    token_bucket_algorithm = token_bucket_algorithm
}

限制并发数

local limit_conn = require "resty.limit.conn"
local cjson = require "cjson"
local baseUtil = require "BaseUtil"

-- 限制最大并发连接数
local function concurrent_connections(rule_detail)
    local key = ngx.var.uri
    local concurrentNum = rule_detail.concurrentNum
    local concurrentExtraNum = rule_detail.concurrentExtraNum
    local concurrentTime = rule_detail.concurrentTime
    -- 检查 concurrentNum、concurrentExtraNum、concurrentTime 的类型
    if type(concurrentNum) ~= "number" or type(concurrentExtraNum) ~= "number" or type(concurrentTime) ~= "number"then
        ngx.log(ngx.ERR, "concurrentNum 或 concurrentExtraNum 或 concurrentTime 的类型不是数字","rule_detail: "..cjson.encode(rule_detail))
        return
    end
    local lim, err = limit_conn.new("api_rate_limit_req_store", concurrentNum, concurrentExtraNum, concurrentTime)
    if not lim then
        ngx.log(ngx.ERR, "限制最大并发连接数,创建限流器失败", err)
        baseUtil.api_rate_limit_res(500,500,"服务器异常")
    end
    local delay, response = lim:incoming(key, true)
    if not delay then
        if response == "rejected" then
            ngx.log(ngx.ERR, "限制请求数 rejected: "," rule_detail: "..cjson.encode(rule_detail).." uri: "..key)
            baseUtil.api_rate_limit_res(429,429,"服务器异常")
        end
        ngx.log(ngx.ERR, "failed to limit req: ", tostring(response).." rule_detail: "..cjson.encode(rule_detail).." uri: "..key)
        baseUtil.api_rate_limit_res(500,500,"服务器异常")
    end

    -- 如果请求没有超过并发限制,那么将连接信息添加到ngx.ctx中
    if lim:is_committed() then
        local ctx = ngx.ctx
        ctx.limit_type = "concurrent_connections"
        ctx.limit_conn = lim
        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

-- 请求离开,减少数量
local function concurrent_connections_leaving()
    local ctx = ngx.ctx
    local lim = ctx.limit_conn
    if lim then
        local key = ctx.limit_conn_key
        local latency = ctx.limit_conn_delay
        local limit_type = ctx.limit_type
        if not limit_type then
            return
        end
        if limit_type ~= "concurrent_connections" then
            return
        end
        --ngx.log(ngx.ERR, "释放连接", "request: ", key.." latency: "..latency)
        local conn, err = lim:leaving(key, latency)
        if not conn then
            ngx.log(ngx.ERR, "failed to record the connection leaving ", "request: ", err.." key: "..key)
            return
        end
    end
end

return {
    concurrent_connections = concurrent_connections,
    concurrent_connections_leaving = concurrent_connections_leaving
}

注意点

  • 实际情况下,生产nginx会存在多台,所以这里将规则存储在redis当中
  • OpenResty本身只支持单机的redis连接,所以这里需要单独引入redis集群模块。参考地址:https://developer.aliyun.com/article/1334650

上面就是几种限流算法用lua实现的关键部分逻辑,代码仅供参考学习,欢迎留言讨论。后续会将完整的项目代码更新到远程仓库,欢迎关注我的公众号,后续会在公众号提供获取地址。下一篇会继续更新使用redis来实现上面几种限流逻辑,进行集群限流
在这里插入图片描述

  • 11
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值