介绍

工作原理

1. 漏斗模型:

  • 漏斗可以看作是一个容器,有一个固定的最大容量(即最大请求速率),漏嘴则控制了流出速率
  • 如果漏斗里的水量超过了当前漏口的处理能力,多余的水会溢出(即请求被限流或丢弃)

2. 处理流程:

  • 漏斗以固定速率处理请求,类似于水从漏斗底部流出的速度
  • 请求到达时,先判断漏斗当前的水量是否能容纳新的请求。如果可以,则接受请求并将水量增加;否则,拒绝请求或以某种方式处理溢出的请求

应用场景:

  • 网络流量控制:限制流过路由器或服务器的数据包速率,防止网络拥塞
  • API请求管理:限制API接口的访问频率,避免服务器过载
  • 消息队列消费速率控制:平滑处理从消息队列中获取的消息,防止消费者因处理速度不足导致消息堆积

leaky bucket实现示例:

-- 漏斗限流逻辑
local os_time = os.time
local mt = {}
mt.__index = mt

-- 是否处于正常可通过状态
function mt:watering()
    local t_now = os_time()
    local leak_water = self.rate * (t_now - self.last_ts)
    if leak_water > 0 then
        local left_water = self.water - leak_water
        self.water = (left_water > 0) and left_water or 0
        self.last_ts = t_now
    end
    if self.water < self.cap then
        self.water = self.water + 1
        return true
    end
    return false
end

-- 重置
function mt:reset()
    self.water = 0
end

local M = {}
function M.create_bucket(rate, cap)
    local bucket = {}
    bucket.rate = rate -- 漏斗每秒速率
    bucket.cap = cap -- 漏斗容量
    bucket.water = 0 -- 漏斗当前的水位
    bucket.last_ts = 0 -- 最近一次处理的时间
    return setmetatable(bucket, mt)
end

return M

搭配pool池

pool.lua示例

用于管理可复用对象的创建、获取和释放
-- object pool, from: https://github.com/treamology/pool.lua

local type, pairs, table, setmetatable = type, pairs, table, setmetatable 
local pool = {}
local poolmt = {__index = pool}

function pool.create(newObject, poolSize, maxPoolSize)
    if type(newObject) ~= "function" then
        return nil
    end

    poolSize = poolSize or 16
    maxPoolSize = (maxPoolSize and maxPoolSize >= poolSize) and maxPoolSize or 2 * poolSize
    local freeObjects = {}
    for _ = 1, poolSize do
        table.insert(freeObjects, newObject())
    end

    return setmetatable({
            freeObjects = freeObjects,
            newObject = newObject,
            maxPoolSize = maxPoolSize
        },
        poolmt
    )
end

function pool:obtain()
    return #self.freeObjects == 0 and self.newObject() or table.remove(self.freeObjects)
end

function pool:free(obj, ...)
    if not obj then
        return false
    end
    if #self.freeObjects < self.maxPoolSize then
        table.insert(self.freeObjects, obj)
    end

    if obj.reset then obj.reset(obj, ...) end
    return true
end

function pool:clear()
    for k in pairs(self.freeObjects) do
        self.freeObjects[k] = nil
    end
end

return pool

搭配示例

local leaky_bucket = require "leaky_bucket"
local pool = require "pool"
local bucketPool = nil -- 漏斗限流池

local buckets = {} -- 漏限流对象池
local function new_bucket_object()
    return leaky_bucket.create_bucket(50, 80)
end
bucketPool = pool.create(new_bucket_object, 100, 200)

local BUCKETS = setmetatable(buckets, {
    __index = function (t, id)
        t[id] = bucketPool:obtain()
        return t[id]
    end
})

对象池(pool)结合漏斗限流(leaky bucket)的好处:

1. 资源重复利用:

  • 对象池(pool)可以有效管理和重复利用漏斗限流对象。通过调用 pool.create(new_bucket_object, m, n) 创建的 bucketPool 对象池,可以预先创建并维护多个漏斗限流对象
  • 当需要使用漏斗限流对象时,通过 bucketPool:obtain() 方法从对象池中获取一个可用对象,而不是每次都重新创建

2. 减少资源消耗:

  • 漏斗限流对象通常包含一些初始化和配置,创建和销毁开销较大。通过对象池,可以避免频繁地创建和销毁漏斗限流对象,从而减少系统资源的消耗,提高性能和效率

3. 提高系统响应速度:

  • 由于对象池中已经预先创建了一定数量的漏斗限流对象,获取对象时只需从池中取出,并可能调用重置方法进行初始化,而不需要完全重新创建。这样可以大大缩短获取对象的响应时间,提高系统对漏斗限流需求的响应速度

4. 动态管理和限制:

  • 通过设置 maxPoolSize 参数,可以动态调整对象池的大小,确保不会因为对象池过小而导致获取对象失败。在需要时,可以根据系统负载和需求灵活地调整对象池的大小
06-17 04:27