限流应该是我们开发中经常遇到的了,限流器能保证我们不至于在流量过大的时候服务超过负载,能有效地保证服务的可用和稳定。
go自带有限流器rate,它的本质其实就是令牌桶。用起来也十分简单。我们修改下之前的http服务做一些修改:
func main() { 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) }) }
func defaultHttp(w http.ResponseWriter, r *http.Request) { path := r.URL.Path
if path == "/" { w.Write([]byte("index")) fmt.Println("index") return }
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