无论是单体架构还是微服务架构,开发者向前端提供的 API 接口都存在一定的访问上限。一旦请求频率或并发量超出系统承载范围,就必须考虑接口限流策略,以保障接口的可用性,或在必要时实现降级可用。换句话说,API 接口也需要装上一道“保险丝”,避免突发或异常请求对系统造成过大压力,进而引发服务雪崩甚至系统瘫痪。

go-zero 集成了开箱即用的 限流器,非常适合在高并发、分布式服务治理场景中使用。其中内置了两种限流器,也分别对应两类常见的限流场景:
本文重点介绍 periodlimit 的实现原理与使用方式。
使用
const (
seconds = 1
total = 100
quota = 5
)
// New limiter
l := NewPeriodLimit(seconds, quota, redis.NewRedis(s.Addr(), redis.NodeType), "periodlimit")
// take source
code, err := l.Take("first")
if err != nil {
logx.Error(err)
return true
}
// switch val => process request
switch code {
case limit.OverQuota:
logx.Errorf("OverQuota key: %v", key)
return false
case limit.Allowed:
logx.Infof("AllowedQuota key: %v", key)
return true
case limit.HitQuota:
logx.Errorf("HitQuota key: %v", key)
// todo: maybe we need to let users know they hit the quota
return false
default:
logx.Errorf("DefaultQuota key: %v", key)
// unknown response, we just let the sms go
return true
}periodlimit
go-zero 采用 滑动窗口 计数方式,统计一段时间内同一个资源的访问次数。如果访问量超过设定的 limit,系统就会拒绝后续访问。当然,如果你在同一时间段内访问的是不同资源,并且每个资源的访问次数都没有超过 limit,那么这种情况下依然可以允许大量请求进入。
在分布式系统中,通常会有多个微服务实例同时对外提供服务。那么当大量瞬时流量同时访问同一个资源时,如何保证计数器在分布式环境下依然准确?此外,在统计资源访问次数时,往往涉及多步计算,又该如何保证整个计算过程具备原子性?
go-zero借助redis的incrby实现资源访问计数- 通过
lua script完成整个窗口计算,从而保证计算过程的原子性
下面来看看这段 lua script 中控制限流逻辑的几个关键属性:
-- to be compatible with aliyun redis,
-- we cannot use `local key = KEYS[1]` to reuse thekey
local limit = tonumber(ARGV[1])
local window = tonumber(ARGV[2])
-- incrbt key 1 => key visis++
local current = redis.call("INCRBY", KEYS[1], 1)
-- 如果是第一次访问,设置过期时间 => TTL = window size
-- 因为是只限制一段时间的访问次数
if current == 1 then
redis.call("expire", KEYS[1], window)
return 1
elseif current < limit then
return 1
elseif current == limit then
return 2
else
return 0
end至于上述的 return code,会返回给调用方,再由调用方决定请求后续的处理逻辑:
下面这张图描述了请求进入系统的整体过程,以及请求触发 limit 之后的后续处理情况:
后续处理
如果在某一个时间点,服务突然遭遇大量请求同时涌入,periodlimit 会在极短时间内达到 limit 阈值,而设定的时间窗口此时可能还远未结束。那么,后续请求如何处理,就成了一个必须考虑的问题。
periodlimit 本身并不会直接处理这些超限请求,而是仅返回对应的 code,将后续请求的处理策略交给开发者自行决定。
- 如果不做额外处理,那么最直接的方式就是拒绝请求
- 如果这些请求必须处理,开发者可以借助
mq对请求进行削峰缓冲,以减轻系统瞬时压力 - 也可以采用
tokenlimit,以支持一定程度上的流量突发
所以下一篇文章,我们就来继续聊聊 tokenlimit。
总结
go-zero 中的 periodlimit 限流方案,本质上是基于 redis 计数器实现的,并通过调用 redis lua script 来保证计数过程的原子性。同时,它也能确保在分布式场景下,限流计数依然准确可靠,因此非常适合用于微服务接口限流和高并发流量治理。
不过,这种方案也存在一定缺点。由于它需要记录时间窗口内的访问行为,当请求量特别大时,内存消耗会明显增加,严重时甚至可能带来较大的资源压力。
参考
- go-zero periodlimit
- 分布式服务限流实战,已经为你排好坑了
同时欢迎大家使用 go-zero 并加入我们!
项目地址
https://github.com/tal-tech/go-zero
https://gitee.com/kevwan/go-zero
如果觉得文章不错,欢迎 github 点个 star
