golang限流器

限流应该是我们开发中经常遇到的了,限流器能保证我们不至于在流量过大的时候服务超过负载,能有效地保证服务的可用和稳定。

go自带有限流器rate,它的本质其实就是令牌桶。用起来也十分简单。我们修改下之前的http服务做一些修改:

func main() {
	// ServeMux类型是HTTP请求的多路转接器。它会将每一个接收的请求的URL与一个注册模式的列表进行匹配,并调用和URL最匹配的模式的处理器。
	mux := http.NewServeMux()
	mux.HandleFunc("/", defaultHttp)
	http.ListenAndServe(":8080", middlewareLimit(mux))
}

// 限流桶
var limiter = rate.NewLimiter(rate.Every(time.Second), 1)

func middlewareLimit(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {

		if limiter.Allow() == false {
			fmt.Println("limit")
			return
		}

		next.ServeHTTP(w, r)
	})
}

// 默认http处理
func defaultHttp(w http.ResponseWriter, r *http.Request) {
	path := r.URL.Path

	if path == "/" {
		w.Write([]byte("index"))
		fmt.Println("index")
		return
	}

	// 自定义404
	http.Error(w, "you lost???", http.StatusNotFound)
}

这里的

var limiter = rate.NewLimiter(rate.Every(2*time.Second), 1)

就是定义了一个限流器,生成速率是1 个/s,令牌桶的容量是1。也就是每秒最多能通过2个请求。

我们可以用ab或者快速刷新浏览器来看一下效果

是不是有点类似nginx的限流模块:

limit_req_zone  $binary_remote_addr  zone=one:10m  rate=1r/s;

server {
    ...

    location / {
        #缓存区队列burst=5个,nodelay表示不延期(超过的请求失败),即每秒最多可处理rate+burst个,同时处理rate个。
        limit_req zone=one burst=1 nodelay; 
    }
}

上面的代码有两处注意点:

  • middlewareLimit 可看作一个http server的前置中间件,你可以类比去自己处理复杂的http中间件业务。
  • 限流器的初始化务必在中间件前生成,可以尝试修改代码再测试:
func middlewareLimit(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		limiter := rate.NewLimiter(rate.Every(2*time.Second), 10)
		if limiter.Allow() == false {
			fmt.Println("limit")
			return
		}

		next.ServeHTTP(w, r)
	})
}

此外,Limiter 也有其他的方法:

  • SetLimit(Limit) 动态修改放入令牌的速率
  • SetBurst(int) 动态修改桶大小
  • Wait/WaitN 当没有可用事件时,将阻塞等待
  • Reserve/ReserveN 当没有可用事件时,返回 Reservation,和要等待多久才能获得足够的事件

代码点击见githubgithub.com/puresai/go-example/tree/main/demo9-rate


golang限流器
https://blog.puresai.com/2021/02/12/goexample9/
作者
puresai
许可协议