【Golang】限流浅析

背景

本文只简单介绍一下目前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小时;
    • 遇到分布式部署环境,需要全局管控限流,这时就需要一些更高阶的方案了。

以上。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
在 Go 中,可以使用 channel 实现限流,即通过 channel 的容量控制并发执行的协程数。 示例代码如下: ```go package main import ( "fmt" "time" ) func worker(id int, jobs <-chan int, results chan<- int) { for j := range jobs { fmt.Printf("worker %d 执行任务 %d\n", id, j) time.Sleep(time.Second) // 模拟任务执行时间 results <- j * 2 } } func main() { jobs := make(chan int, 100) // 创建任务 channel,缓冲区大小为 100 results := make(chan int, 100) // 创建结果 channel,缓冲区大小为 100 // 创建 3 个 worker 协程,即最多同时执行 3 个任务 for w := 1; w <= 3; w++ { go worker(w, jobs, results) } // 发送 9 个任务到 jobs channel for j := 1; j <= 9; j++ { jobs <- j } close(jobs) // 关闭 jobs channel,表示所有任务已发送完毕 // 收集所有结果 for a := 1; a <= 9; a++ { <-results } } ``` 在上面的示例中,我们创建了一个 `jobs` channel 和一个 `results` channel,用于分别传递任务和结果。我们创建了 3 个 worker 协程,并将 `jobs` 和 `results` channel 分别传递给它们。在主协程中,我们向 `jobs` channel 发送 9 个任务,并关闭 `jobs` channel,表示所有任务已发送完毕。然后我们收集所有结果。 由于 `jobs` channel 的缓冲区大小为 100,即最多可以存储 100 个任务,而 `results` channel 的缓冲区也为 100,即最多可以存储 100 个结果。因此,当 worker 协程数小于等于 3 时,所有任务都可以立即执行;当 worker 协程数大于 3 时,多余的任务会被存储在 `jobs` channel 中,直到有空闲的 worker 协程可以执行它们。这样就实现了限流的功能。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小镇学者

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值