令牌桶限流器的 Allow、Wait、Reserve 有什么区别?burst 到底是什么?这篇用真实运行回答:桶能攒多少、Wait 摊开请求有多均匀、什么情况下 Wait 会立即报错、多个 goroutine 共用时总吞吐是多少,再读 rate.go 里的说明。
golang.org/x/time v0.16.0
rate 包来自 golang.org/x/time(v0.16.0),不在标准库里。
NewLimiter(10/s, burst 5),桶是满的,连续调用 Allow() 8 次:[true true true true true false false false] 等 105 ms 后再 Allow():true;紧接着再 Allow():false 再空闲 1.2 秒(攒了 12 个令牌,但桶只装 5 个)后,连续 Allow() 成功了 5 次(总用时 1307 ms)
桶满时 Allow 连续放行 burst 个(5),然后被拒绝;空闲 1.2 秒本应攒 12 个令牌,桶装不下,只能一口气放行 5 个。
NewLimiter(20/s, burst 1),连续 Wait 11 次,每次放行的时刻(ms): 0 51 101 151 201 251 301 351 401 451 501 相邻两次放行的间隔:最小 49.9 ms / 最大 51.1 ms(理论 50 ms)
20/s、burst 1 的限流器,Wait 的放行间隔在 49.9–51.1 ms 之间,几乎就是理论的 50 ms。
WaitN(ctx, 6)(超过桶容量 5):rate: Wait(n=6) exceeds limiter's burst 5 速率 1/s,桶已空,ctx 期限 200 ms,Wait 返回:rate: Wait(n=1) would exceed context deadline(耗时 0 ms,没有真的等 1 秒)
WaitN 的 n 超过 burst 会立即报错;如果预计等待时间会超出 ctx 的期限,Wait 也立即返回 “would exceed context deadline”,不会白白等到超时。
桶空时 Reserve():OK=true,Delay=100 ms Cancel 之后再 Reserve():Delay=100 ms(取消把令牌还回去了) 不 Cancel,连续 Reserve 3 次:Delay = 100 / 200 / 300 ms(预约一个比一个往后排)
Reserve 返回需要等待多久,不 Cancel 的话预约会占用未来的令牌:连续 3 次的延迟是 100、200、300 ms。要放弃就调 Cancel 把令牌还回去。
限速 1000/s,1 个 goroutine 共用一个限流器各自循环 Wait,3 秒内通过 3010 次(1003 次/秒) 限速 1000/s,8 个 goroutine 共用一个限流器各自循环 Wait,3 秒内通过 3017 次(1003 次/秒) 限速 1000/s,64 个 goroutine 共用一个限流器各自循环 Wait,3 秒内通过 3073 次(1003 次/秒)
限速 1000/s 时,无论 1 个、8 个还是 64 个 goroutine 一起等,总吞吐都是约 1003 次/秒。限流器是整个进程的总量,不是每个 goroutine 各 1000。
Allow():rate.Inf 45 ns/次;有限速率(单线程)66 ns/次;有限速率(10 个 P 并行)200 ns/次
Allow 单线程 66 ns,10 个 P 争同一个限流器 200 ns;速率为 Inf 时 45 ns。对每个请求限流没问题,但每秒几百万次的热点路径要注意。
// A Limiter controls how frequently events are allowed to happen. // It implements a "token bucket" of size b, initially full and refilled // at rate r tokens per second. // Informally, in any large enough time interval, the Limiter limits the // rate to r tokens per second, with a maximum burst size of b events. // As a special case, if r == Inf (the infinite rate), b is ignored. // See https://en.wikipedia.org/wiki/Token_bucket for more about token buckets. // // The zero value is a valid Limiter, but it will reject all events. // Use NewLimiter to create non-zero Limiters. // // Limiter has three main methods, Allow, Reserve, and Wait. // Most callers should use Wait. // // Each of the three methods consumes a single token. // They differ in their behavior when no token is available. // If no token is available, Allow returns false. // If no token is available, Reserve returns a reservation for a future token // and the amount of time the caller must wait before using it. // If no token is available, Wait blocks until one can be obtained // or its associated context.Context is canceled. // // The methods AllowN, ReserveN, and WaitN consume n tokens. // // Limiter is safe for simultaneous use by multiple goroutines.
注释写明:令牌桶初始是满的;零值合法但拒绝所有事件;多数调用者应该用 Wait;可以被多个 goroutine 同时使用。