漏斗限流(leaky bucket)
介绍
工作原理
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 参数,可以动态调整对象池的大小,确保不会因为对象池过小而导致获取对象失败。在需要时,可以根据系统负载和需求灵活地调整对象池的大小