
最近在做组件化,组件化其中有一点就是需要有限流能力,针对不同的租户设置不同的限流上限,避免其中一个租户请求量过高影响其他租户。
限流器是一种服务治理能力,用于限制服务的并发调用量,以保护服务的稳定性。
限流一般有单节点限流、集群限流(将限流数值对集群节点数求平均值,其本质还是单节点限流)、分布式限流。本文暂时只讨论单机的。
限流器类型方面一般分为并发数限流和速率限流两种。
并发数限流顾名思义,就是限制并发数,不让并发数过高
本次使用的是gozero的限流器
github.com/zeromicro/go-zero/core/syncx初始化
zeroLimit = syncx.NewLimit(100)使用
func (s *helloServerImpl) SayHello(ctx context.Context, req *hellopb.HelloRequest) (*hellopb.HelloReply, error) {
if !zeroLimit.TryBorrow()() {
return nil, status.Errorf(codes.ResourceExhausted, "rate limit exceeded")
}
defer zeroLimit.Return() // 务必务必
time.Sleep(200 * time.Millisecond)
return &hellopb.HelloReply{
Message: "Hello " + req.Name,
}, nil
}这里一定要注意zeroLimit.Return()
因为内部是使用信号量去实现的,通过拿资源->归还资源来控制。
// TryBorrow() - 非阻塞获取资源
func (l Limit) TryBorrow() bool {
select {
case l.pool <- lang.Placeholder: // 向 channel 发送信号
return true
default:
return false // 资源已满,获取失败
}
}
// Return() - 归还资源
func (l Limit) Return() error {
select {
case <-l.pool: // 从 channel 接收信号,释放资源
return nil
default:
return ErrLimitReturn // 归还过多资源
}
}如果忘记调用Return(),后果很严重
因为造成了信号量的资源泄露,一直没有归还,导致限流器认为还有其他并发,结果就是导致后面的请求都被拒绝。


速率限流就是控制每秒请求数的,跟我们直觉上任务的限流是一致的。
使用的框架为官方库
golang.org/x/time/rate初始化
rateLimit := rate.Limit(100)
// 创建限流器
limiter = rate.NewLimiter(rateLimit, 100)使用上比较简单,没有什么要特别注意的事项,不需要归还资源。
func (s *helloServerImpl) SayHello(ctx context.Context, req *hellopb.HelloRequest) (*hellopb.HelloReply, error) {
// if !zeroAllow() {
// return nil, status.Errorf(codes.ResourceExhausted, "rate limit exceeded")
// }
// defer zeroLimit.Return()
if !limiter.Allow() {
return nil, status.Errorf(codes.ResourceExhausted, "rate limit exceeded")
}
time.Sleep(200 * time.Millisecond)
return &hellopb.HelloReply{
Message: "Hello " + req.Name,
}, nil
}2000个请求花费了484毫秒,我们设置的是1秒内最多100个请求,算是限流成功,成功数量有波动正常,因为要算上桶的初始量(100)+这段时间内新生成的令牌(允许突发流量的特性)

两种限流器各有适用场景,需要根据实际场景选用,当然,也可以组合使用,效果更佳,但是也更复杂。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。