后台工程师来讲讲限流的思路
Posted yzlit
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了后台工程师来讲讲限流的思路相关的知识,希望对你有一定的参考价值。
一 ”两窗两桶“限流算法
1、固定窗口
固定窗口指的是为一定时间段的流量设置一个阈值,超过则触发限流策略(丢弃或者停留),然后直到下一个时间段重新置零开始计数,
这种策略很明显一个缺点是这个时间段的间隔要设置得好,否则会存在一个问题,就是无法“削峰填谷”,当时间间隔设为10秒100个请求量时,本来是应该正常每秒10个请求,但是突然第一秒就来了100个请求,在第一秒就变成100个请求每秒了,所以是没法达到限流缓冲的效果,所以这种策略比较粗暴简单。
2、滑动窗口
滑动窗口算法 类似于我们学过TCP的滑动窗口,对固定窗口的一种优化,
1、四种策略该如何选择?
首先,固定窗口。一般来说,如非时间紧迫,不建议选择这个方案,太过生硬。但是,为了能快速止损眼前的问题可以作为临时应急的方案。
其次,滑动窗口。这个方案适用于对异常结果「高容忍」的场景,毕竟相比“两窗”少了一个缓冲区。但是,胜在实现简单。
然后,漏桶。个人觉得这个方案最适合作为一个通用方案。虽说资源的利用率上不是极致,但是「宽进严出」的思路在保护系统的同时还留有一些余地,使得它的适用场景更广。
最后,令牌桶。当你需要尽可能的压榨程序的性能(此时桶的最大容量必然会大于等于程序的最大并发能力),并且所处的场景流量进入波动不是很大(不至于一瞬间取完令牌,压垮后端系统)。
三、如何做分布式限流
如果要分布式限流,那么单机限流的策略其实只需要移植一下,用新的工具如redis+lua 或者nginx+lua 又或者用zookeeper也是可以实现的,下面先具体讲讲redis+lua的分布式限流的思路和代码例子,其他有空再补上。
redis做限流的思路是先设置一个key=service1的记录,值为流量,和时间戳。这些用lua来主要是因为redis不支持事务,而lua脚本是作为一个原子操作一起执行的,lua脚本也是会存在reids内存中作为经常执行的命令串。
下面是固定窗口算法的lua脚本
1 local key = KEYS[1] 2 local limit = tonumber(KEYS[2]) ## 限流 3 local current = tonumber(redis.call(‘get‘, key) or 0) 4 ? 5 if current + 1 > limit then 6 return 0 7 else 8 redis.call("INCRBY", key,"1") ## 对key 加1操作 9 redis.call("EXPIRE", key,"10") ## 过期时间设为10秒 10 return 1 11 end
下面是令牌桶算法的lua脚本
1 --- 获取令牌 2 --- 返回码 3 --- 0 没有令牌桶配置 4 --- -1 表示取令牌失败,也就是桶里没有令牌 5 --- 1 表示取令牌成功 6 --- @param key 令牌(资源)的唯一标识 7 --- @param permits 请求令牌数量 8 --- @param curr_mill_second 当前毫秒数 9 --- @param context 使用令牌的应用标识 10 ? 11 ? 12 local function acquire(key, permits, curr_mill_second, context) 13 redis.pcall("HSET", key, "max_permits", "5") 14 local rate_limit_info = redis.pcall("HMGET", key, "last_mill_second", "curr_permits", "max_permits", "rate", "apps") 15 local last_mill_second = rate_limit_info[1] 16 local curr_permits = tonumber(rate_limit_info[2]) 17 local max_permits = tonumber(rate_limit_info[3]) 18 local rate = rate_limit_info[4] 19 local apps = rate_limit_info[5] 20 --- 标识没有配置令牌桶 21 if type(apps) == ‘boolean‘ or apps == nil or string.find(apps, context, 1)~=nil then 22 redis.pcall("HSET", key, "apps", context) 23 apps=context 24 end 25 if type(rate) == ‘boolean‘ or rate == nil then 26 redis.pcall("HSET", key, "rate", "0.2") 27 rate=0.2 28 end 29 if type(curr_permits) == ‘boolean‘ or curr_permits == nil then 30 redis.pcall("HSET", key, "curr_permits", "0") 31 end 32 local local_curr_permits = max_permits; 33 --- 令牌桶刚刚创建,上一次获取令牌的毫秒数为空 34 --- 根据和上一次向桶里添加令牌的时间和当前时间差,触发式往桶里添加令牌 35 --- 并且更新上一次向桶里添加令牌的时间 36 --- 如果向桶里添加的令牌数不足一个,则不更新上一次向桶里添加令牌的时间 37 if (type(last_mill_second) ~= ‘boolean‘ and last_mill_second ~= false and last_mill_second ~= nil) then 38 local reverse_permits = math.floor(((curr_mill_second - last_mill_second) / 1000) * rate) 39 local expect_curr_permits = reverse_permits + curr_permits; 40 local_curr_permits = math.min(expect_curr_permits, max_permits); 41 --- 大于0表示不是第一次获取令牌,也没有向桶里添加令牌 42 if (reverse_permits > 0) then 43 redis.pcall("HSET", key, "last_mill_second", curr_mill_second) 44 end 45 else 46 redis.pcall("HSET", key, "last_mill_second", curr_mill_second) 47 end 48 local result = -1 49 if (local_curr_permits - permits >= 0) then 50 result = 1 51 redis.pcall("HSET", key, "curr_permits", local_curr_permits - permits) 52 else 53 redis.pcall("HSET", key, "curr_permits", local_curr_permits) 54 end 55 return result 56 end 57 ? 58 ? 59 return acquire(KEYS[1],tonumber(KEYS[2]),tonumber(KEYS[3]),KEYS[4])
以上是关于后台工程师来讲讲限流的思路的主要内容,如果未能解决你的问题,请参考以下文章