限流
在大部分应用中,为了对服务进行保护,我们通常都会添加限流策略,比如:限制同一用户一段时间内验证码的发送频率、开放API平台,限制每个应用在单位时间内的API调用次数、单节点限制最大并发数据,防止压垮服务等等。
今天我们一起来看看在go-zero中限流怎么玩?
go-zero中的限流主要有三种,我们依次看下。
一、 配置MaxConns
它是一种http单节点的限流,它限制的是当前节点的并发量(并发数)。
它**最简单,只需要我们在api
模块下的etc
目录配置MaxConns
**就行,其它啥也不用做,如下:
yaml
Name: limit
Host: 0.0.0.0
Port: 8888
MaxConns: 100
PS: 默认情况下MaxConns
为10000.
原理
它是通过syncx.Limit
实现的。
它的机制是一种连接池机制,一开始有一个池,这个池子有一个最大的并发量。
-
每次请会先从池子借用一个连接
-
使用完后归还连接
当全部借出后,则不再处理请求,提示(concurrent connections over ...
).
它的使用非常简单,就是三步:
-
初始化池子(sync.Limit)
-
借用
-
归还
代码如下:
go
package main
import (
"fmt"
"log"
"github.com/zeromicro/go-zero/core/syncx"
)
func main() {
limit := syncx.NewLimit(2)
if limit.TryBorrow() {
fmt.Println("借用成功啦")
} else {
fmt.Println("借用失败")
}
err := limit.Return()
if err != nil {
log.Println("归还失败:", err)
} else {
fmt.Println("归还成功")
}
}
二、令牌通限流
上面我们已经介绍了针对于http
请求场景的单节点限流,它的粒度相对较大。在一些其它场景还是不能满足我们的要求。
比如:比如我们希望对某一个细分模块限流,比如发送短信模块,我们希望每秒内最多发送100条短信,那么我们就可以使用令牌桶限流。
它限制的是:每秒内的发生频率,且支持一个范围,一个最小值和一个最大值------应对突发情况.
PS:它的使用要复杂点,依赖于redis
代码:
go
package main
import (
"fmt"
"github.com/zeromicro/go-zero/core/limit"
"github.com/zeromicro/go-zero/core/stores/redis"
)
func main() {
store, err := redis.NewRedis(redis.RedisConf{Host: "127.0.0.1:6379", Type: "node"})
if err != nil {
panic(err)
}
// 每秒中内 可以处理 5-10个请求(最大20个超过就爆掉)
// example-key 是redis中标记(或者限制模块的key)
limiter := limit.NewTokenLimiter(5, 10, store, "example-key")
// 我们测试
for i := 0; i < 20; i++ {
// 是否允许
if limiter.Allow() {
fmt.Println("Request allowed") // 前10个成功
} else {
fmt.Println("Request not allowed") // 后10个失败
}
}
}
// Request allowed
// Request allowed
// Request allowed
// Request allowed
// Request allowed
// Request allowed
// Request allowed
// Request allowed
// Request allowed
// Request allowed
// Request not allowed
// Request not allowed
// Request not allowed
// Request not allowed
// Request not allowed
// Request not allowed
// Request not allowed
// Request not allowed
// Request not allowed
// Request not allowed
前10个请求成功,后10个请求失败,符合我们的预期。
三、计数器(时间内限流)
前面我们限制的是1s内处理数,但是在很多场景下,通常我们限制的是一个时间范围内的请求次数
比如:一个用户在一分钟内,只能获取2次验证码。
它通常是更细分、用户级的限流
它也依赖于redis
它的使用大体和令牌桶类似,只是它的参数不同,如下:
go
package main
import (
"fmt"
"github.com/zeromicro/go-zero/core/limit"
"github.com/zeromicro/go-zero/core/stores/redis"
)
func main() {
store, err := redis.NewRedis(redis.RedisConf{Host: "127.0.0.1:6379", Type: "node"})
if err != nil {
panic(err)
}
// 这里限制60秒内 最多允许2次
// 注意这里的exampleKey只是一个前缀(具体的key后半部分由场景决定)
limiter := limit.NewPeriodLimit(60, 2, store, "exampleKey")
for i := 0; i < 10; i++ {
// 这里的限制用户的请求次数
result, err := limiter.Take("userId")
if err != nil {
fmt.Println("Error:", err)
return
}
switch result {
case limit.Allowed:
fmt.Println("Request allowed")
case limit.HitQuota: // 代表达到临界点(下一次就超了)
fmt.Println("Hit the quota")
case limit.OverQuota:
fmt.Println("Over the quota")
default:
fmt.Println("Unknown status")
}
}
}
// Request allowed
// Hit the quota
// Over the quota
// Over the quota
// Over the quota
// Over the quota
// Over the quota
// Over the quota
// Over the quota
// Over the quota