背景
本文只简单介绍一下目前google官方限流器的用法,不作深入研究。结合gin框架中间件的做法迅速完成一个简单的限流器。
代码
package main
import (
"context"
"net/http"
"sync"
"time"
"github.com/gin-gonic/gin"
"golang.org/x/time/rate"
)
func NewLimiter(r rate.Limit, b int, t time.Duration) gin.HandlerFunc {
limiters := &sync.Map{}
return func(c *gin.Context) {
// 获取限速器
// key 除了 ip 之外也可以是其他的,例如 header,user name 等
key := c.ClientIP()
l, _ := limiters.LoadOrStore(key, rate.NewLimiter(r, b))
// 这里注意不要直接使用 gin 的 context 默认是没有超时时间的
ctx, cancel := context.WithTimeout(c, t)
defer cancel()
if err := l.(*rate.Limiter).Wait(ctx); err != nil {
// 这里可以记录被禁止访问的IP到日志或redis
c.AbortWithStatusJSON(http.StatusTooManyRequests, gin.H{"error": err})
}
c.Next()
}
}
func main() {
r := gin.Default()
// 新建一个限速器,允许突发 10 个并发,限速 3rps,超过 500ms 就不再等待
r.Use(NewLimiter(3, 10, 500*time.Millisecond))
r.GET("/", func(c *gin.Context) {
c.JSON(200, gin.H{
"message": "hello world",
})
})
r.Run() // 监听并在 0.0.0.0:8080 上启动服务
}
浏览器访问案例
提示:这里需要把相应的阀值调小一点,我是将r、b都设置为1了,然后再页面上频繁刷新,才得出如下结果。
wrk压测案例
wrk安装和参数释义请见我另外一篇文章:
wrk http压测工具安装及简单对比go、gin、php、lumen
注意:最后一个用时500ms,就是我们设置的等待值;具体原理大家可以见另外一篇文章。
apipost压测成功案例
由于apipost压测性能比较差,我是将r、b都设置为1了。
apipost压测失败案例
apipost配置执行100次,间隔1ms;令牌桶配置:r=3 b=10。
注意:从第11个请求开始,响应时长明显增加了,因为令牌桶的生产效率配置的很低。
总结
- 优点:
- 加上内存限流,性能上肯定有所损耗,但这个损耗不足为惧;
- 简单明了,快速集成;
- 缺点:
- 实际生产项目中,我们可能考虑到更多细节问题,比如捕捉到某个触发封禁规则的IP:需要加入到黑名单,进行永久封禁;或者禁止访问24小时;
- 遇到分布式部署环境,需要全局管控限流,这时就需要一些更高阶的方案了。
以上。