一. 限流与time/rate基础
- golang内部基于令牌桶算法提供了一个限流器time/rate位于golang.org/x/time/rate
- 在golang中,可以使用channel或者使用time/rate包实现并发控制, time/rate包是一个提供令牌桶算法的包,它可以实现对事件发生的速率进行限制和平滑,使用time/rate控制并发比使用channel控制并发的优点在于
- time/rate只需要调用NewLimiter()函数,设置并发限制规则创建Limiter对象,然后在每个goroutine中调用Wait或者Allow方法来获取令牌即可,简单方便,channel需要创建一个缓冲区,然后在每个goroutine中通过向channel的缓冲区存放或获取数据来控制并发
- time/rate可以更灵活地控制并发的速率,可以根据令牌桶的容量和填充速度来调整并发的上限和平均值,而channel只能根据缓冲区的大小来控制并发的上限,不能控制平均值
- time/rate可以更容易地处理并发的异常情况,例如超时或者取消:提供了WaitN、Reserve、Context等方法来支持超时或者取消的场景,而channel需要额外的逻辑来处理超时或者取消,例如使用select语句或者context包
- WaitN方法和Reserve方法都可以获取n个令牌,它们以后什么不同
- WaitN方法会阻塞当前goroutine,直到获取到n个令牌,或者超时或者取消,它接收一个context参数,用于控制超时或者取消的行为,如果获取成功,它会返回nil,否则返回一个错误
- Reserve方法会预定n个令牌,返回一个Reservation对象用于表示预定的结果,Reservation对象提供了Delay方法,用于获取预定的延迟时间,以及Cancel方法用于取消预定。如果预定成功它会返回一个非nil的Reservation对象否则返回nil
- 一般来说如果想要同步地等待令牌,或者不关心延迟时间,你可以使用WaitN方法,如果想要异步地等待令牌,或者想要知道延迟时间,你可以使用Reserve方法
- time/rate常用的API概述:
- NewLimiter(r Limit, b int) *Limiter: 创建一个新的限流器,r表示每秒可以向令牌桶中产生多少令牌,b表示最大容量
- Limit() Limit: 返回限流器的最大事件频率
- Burst() int: 返回限流器的最大突发大小,即一次可以消费的最大令牌数1
- Allow() bool: 相当于AllowN(time.Now(), 1),表示是否可以消费一个令牌,可以返回true并消费一个令牌
- AllowN(t time.Time, n int) bool: 表示是否可以消费n个令牌,可以则返回true并消费n个令牌
- Reserve() Reservation: 相当于ReserveN(time.Now(), 1),表示预订一个令牌,并返回一个Reservation对象,该对象可以用来获取需要等待的时间或者取消预订
- ReserveN(t time.Time, n int) Reservation: 表示预订n个令牌,并返回一个Reservation对象
- Wait(ctx context.Context) (err error): 相当于WaitN(ctx, 1),表示等待直到可以消费一个令牌,或者通过ctx被取消
- WaitN(ctx context.Context, n int) (err error): 表示等待直到可以消费n个令牌,或者通过ctx被取消
- 预订的令牌时,返回的Reservation结构体上常用的API:
- Cancel(): 取消预订,将令牌归还给限流器
- CancelAt(t time.Time): 在指定的时间取消预订,将令牌归还给限流器
- Delay(): 返回需要等待的时间,相当于DelayFrom(time.Now())
- DelayFrom(t time.Time): 返回从指定的时间开始需要等待的时间
- OK():返回预订是否有效,如果预订的令牌数超过了限流器的突发大小(也就是最大个数),预定无效返回false
- time/rate基础使用示例
import (
"context"
"fmt"
"github.com/gin-gonic/gin"
"golang.org/x/time/rate"
"log"
"time"
)
func main() {
limiter := rate.NewLimiter(rate.Every(time.Millisecond*100), 20)
bo := limiter.Allow()
if bo {
fmt.Println("获取令牌成功")
}
limiter.AllowN(time.Now(), 2)
ctx, _ := context.WithTimeout(context.Background(), time.Second*10)
limiter.Wait(ctx)
err := limiter.WaitN(ctx, 20)
if err != nil {
fmt.Println("error", err)
}
reservation := limiter.Reserve()
if 0 == reservation.Delay() {
fmt.Println("获取令牌成功")
}
limiter.ReserveN(time.Now(), 1)
limiter.SetLimit(rate.Every(time.Millisecond * 100))
limiter.SetLimitAt(time.Now(), rate.Every(time.Millisecond*100))
limiter.SetBurst(50)
limiter.SetBurstAt(time.Now(), 50)
l := limiter.Limit()
fmt.Printf("每秒允许处理的事件数量,即每秒处理事件的频率为: %v", l)
limiter.Burst()
}
WaitN实现超时取消示例
- time/rate基于WaitN方法实现超时控制:限制每秒只允许10个请求,最大允许请求数为20,当超过20个后,超过部分等待30秒,如果30秒还获取不到则拒绝,示例中写明中文注释,写明哪里是拒绝执行的,哪里是放行执行的
package main
import (
"context"
"fmt"
"golang.org/x/time/rate"
"time"
)
func main() {
limiter := rate.NewLimiter(10, 20)
for i := 0; i < 100; i++ {
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
err := limiter.WaitN(ctx, 1)
if err != nil {
fmt.Println(i, "rejected:", err)
continue
}
fmt.Println(i, "accepted")
}
}
- time/rate基于WaitN方法实现超时控制:
- 限制每秒只允许10个请求,最大允许请求数为20,
- 当请求超过20个后,判断当前是否有正在阻塞等待的请求,如果有判断阻塞等待的请求数是否超过5个,如果阻塞等待请求超过5个直接拒绝请求
- 如果没有阻塞等待请求,或者阻塞等待请求没有超过5个,阻塞等待30秒,如果30秒未执行,响应超时
package main
import (
"context"
"fmt"
"golang.org/x/time/rate"
"sync/atomic"
"time"
)
func main() {
limiter := rate.NewLimiter(10, 20)
var waiting int64 = 0
for i := 0; i < 100; i++ {
if atomic.LoadInt64(&waiting) > 5 {
fmt.Println(i, "rejected: too many waiting requests")
continue
}
atomic.AddInt64(&waiting, 1)
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
err := limiter.WaitN(ctx, 1)
if err != nil {
fmt.Println(i, "rejected:", err)
continue
}
atomic.AddInt64(&waiting, -1)
fmt.Println(i, "accepted")
}
}
ReserveN预定令牌示例
- 在执行Limiter下的Reserve()或ReserveN()方法时会返回一个Reservation,Reservation下的方法使用示例,注意ReserveN只能预订最大限制以内的,如果ReserveN预定数量超过了最大限制
func test() {
limiter := rate.NewLimiter(rate.Every(time.Millisecond*100), 20)
reservation := limiter.Reserve()
if !reservation .OK() {
}
if 0 == reservation.Delay() {
fmt.Println("获取令牌成功")
}
reservation.DelayFrom(time.Now())
if reservation.OK() {
fmt.Println("获取令牌成功")
}
reservation.Cancel()
reservation.CancelAt(time.Now())
}
使用示例
- 需求: 根据调用服务或者接口限流
- 指定服务或接口有限流要求,获取到限流规则
- 编写限流逻辑,创建限流结构,限流容器,获取限流limit方法
- 提供限流中间件,获取到请求后获取限流limit,如果超出阈值熔断
- 限流逻辑
import (
"golang.org/x/time/rate"
"sync"
)
type FlowLimiterItem struct {
ServiceName string
Limter *rate.Limiter
}
type FlowLimiter struct {
FlowLmiterMap map[string]*FlowLimiterItem
FlowLmiterSlice []*FlowLimiterItem
Locker sync.RWMutex
}
func NewFlowLimiter() *FlowLimiter {
return &FlowLimiter{
FlowLmiterMap: map[string]*FlowLimiterItem{},
FlowLmiterSlice: []*FlowLimiterItem{},
Locker: sync.RWMutex{},
}
}
var FlowLimiterHandler *FlowLimiter
func init() {
FlowLimiterHandler = NewFlowLimiter()
}
func (counter *FlowLimiter) GetLimiter(serverName string, qps float64) (*rate.Limiter, error) {
for _, item := range counter.FlowLmiterSlice {
if item.ServiceName == serverName {
return item.Limter, nil
}
}
newLimiter := rate.NewLimiter(rate.Limit(qps), int(qps*3))
item := &FlowLimiterItem{
ServiceName: serverName,
Limter: newLimiter,
}
counter.FlowLmiterSlice = append(counter.FlowLmiterSlice, item)
counter.Locker.Lock()
defer counter.Locker.Unlock()
counter.FlowLmiterMap[serverName] = item
return newLimiter, nil
}
- 编写限流中间件
import (
"fmt"
"github.com/e421083458/go_gateway/dao"
"github.com/e421083458/go_gateway/middleware"
"github.com/e421083458/go_gateway/public"
"github.com/gin-gonic/gin"
"github.com/pkg/errors"
)
func HTTPFlowLimitMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
serverInterface, ok := c.Get("service")
if !ok {
middleware.ResponseError(c, 2001, errors.New("service not found"))
c.Abort()
return
}
serviceDetail := serverInterface.(*dao.ServiceDetail)
if serviceDetail.AccessControl.ServiceFlowLimit != 0 {
serviceLimiter, err := public.FlowLimiterHandler.GetLimiter(
public.FlowServicePrefix+serviceDetail.Info.ServiceName,
float64(serviceDetail.AccessControl.ServiceFlowLimit))
if err != nil {
middleware.ResponseError(c, 5001, err)
c.Abort()
return
}
if !serviceLimiter.Allow() {
middleware.ResponseError(c, 5002, errors.New(fmt.Sprintf("service flow limit %v", serviceDetail.AccessControl.ServiceFlowLimit)))
c.Abort()
return
}
}
if serviceDetail.AccessControl.ClientIPFlowLimit > 0 {
clientLimiter, err := public.FlowLimiterHandler.GetLimiter(
public.FlowServicePrefix+serviceDetail.Info.ServiceName+"_"+c.ClientIP(),
float64(serviceDetail.AccessControl.ClientIPFlowLimit))
if err != nil {
middleware.ResponseError(c, 5003, err)
c.Abort()
return
}
if !clientLimiter.Allow() {
middleware.ResponseError(c, 5002, errors.New(fmt.Sprintf("%v flow limit %v", c.ClientIP(), serviceDetail.AccessControl.ClientIPFlowLimit)))
c.Abort()
return
}
}
c.Next()
}
}
- 该方式也可以再提取出一个针对租户限流的中间件
import (
"fmt"
"github.com/e421083458/go_gateway/dao"
"github.com/e421083458/go_gateway/middleware"
"github.com/e421083458/go_gateway/public"
"github.com/gin-gonic/gin"
"github.com/pkg/errors"
)
func HTTPJwtFlowLimitMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
appInterface, ok := c.Get("app")
if !ok {
c.Next()
return
}
appInfo := appInterface.(*dao.App)
if appInfo.Qps > 0 {
clientLimiter, err := public.FlowLimiterHandler.GetLimiter(
public.FlowAppPrefix+appInfo.AppID+"_"+c.ClientIP(),
float64(appInfo.Qps))
if err != nil {
middleware.ResponseError(c, 5001, err)
c.Abort()
return
}
if !clientLimiter.Allow() {
middleware.ResponseError(c, 5002, errors.New(fmt.Sprintf("%v flow limit %v", c.ClientIP(), appInfo.Qps)))
c.Abort()
return
}
}
c.Next()
}
}
二. time/rate 底层原理相关
Limiter 与 Reservation 结构
- 在调用NewLimiter()创建限流器时会返回一个Limiter结构体变量,查看内部组成:
type Limiter struct {
limit Limit
burst int
mu sync.Mutex
tokens float64
last time.Time
lastEvent time.Time
}
- 当调用Reserve()预定令牌时,会返回一个Reservation结构体变量
type Reservation struct {
ok bool
lim *Limiter
tokens int
timeToAct time.Time
limit Limit
}
消费令牌底层原理
- 在rate中默认提供了Allow()消费一个令牌,Wait()阻塞等待消费一个令牌, Reserve()预定一个令牌,实际底层对应调用的是WaitN(), AllowN(), ReserveN()消费指定个数令牌的方法
- 所有消费令牌的方法内部都会调用reserveN和advance方法,reserveN可以理解为预约令牌的逻辑,由于可以预约,当桶中令牌不够时,预定过后桶中令牌有可能为负数
- 以Allow()消费一个令牌为例,内部会调用AllowN(), AllowN内部会调用reserveN(),reserveN内部会调用advance()
func (lim *Limiter) Allow() bool {
return lim.AllowN(time.Now(), 1)
}
func (lim *Limiter) AllowN(t time.Time, n int) bool {
return lim.reserveN(t, n, 0).ok
}
1. reserveN()方法逻辑
- reserveN是 AllowN, ReserveN及 WaitN的辅助方法,主要用于判断在maxFutureReserve指定时间内是否有足够的令牌
func (lim *Limiter) reserveN(t time.Time, n int, maxFutureReserve time.Duration) Reservation {
lim.mu.Lock()
defer lim.mu.Unlock()
if lim.limit == Inf {
return Reservation{
ok: true,
lim: lim,
tokens: n,
timeToAct: t,
}
} else if lim.limit == 0 {
var ok bool
if lim.burst >= n {
ok = true
lim.burst -= n
}
return Reservation{
ok: ok,
lim: lim,
tokens: lim.burst,
timeToAct: t,
}
}
t, tokens := lim.advance(t)
tokens -= float64(n)
var waitDuration time.Duration
if tokens < 0 {
waitDuration = lim.limit.durationFromTokens(-tokens)
}
ok := n <= lim.burst && waitDuration <= maxFutureReserve
r := Reservation{
ok: ok,
lim: lim,
limit: lim.limit,
}
if ok {
r.tokens = n
r.timeToAct = t.Add(waitDuration)
lim.last = t
lim.tokens = tokens
lim.lastEvent = r.timeToAct
}
return r
}
- 对reserveN的流程总结
- 调用NewLimiter()创建限流器时会返回一个Limiter结构体变量,内部存在一个limit每秒允许处理的事件数量,与burst最大通过数量属性
- 在调用reserveN()消费令牌时,首先会加锁,判断limit是否等于MaxFloat64,如果是说明无限的速率限制桶中一直拥有足够的令牌,直接返回true
- 如果limit等于0,判断当前获取的令牌数量是否超过了burst最大并发限制,如果超过了返回false,没超过返回true
- 如果limit不等于MaxFloat64并且不等于0,调用advance()计算当前可以使用的令牌数量,也就是(上次剩余令牌数+上次消费令牌时间到当前时间所生成的令牌,如果大于最大并发限制,的更新为最大并发限制)
- 如果advance()拿到当前可消费的令牌数量小于0,说明超过并发限制,调用durationFromTokens()计算等待生成令牌时间
- 封装Reservation,如果当前要消费的令牌数量小于允许可消费的令牌数量则Reservation中的ok为true表示允许消费,否则为false
- 最后解锁,返回预约结果
2. advance()根据当前时间和上次更新时间计算桶中剩余的令牌数逻辑
- 该方法的作用是更新令牌桶的状态,计算出令牌桶未更新的时间(elapsed),根据elapsed算出需要向桶中加入的令牌数delta,然后算出桶中可用的令牌数newTokens
func (lim *Limiter) advance(t time.Time) (newT time.Time, newTokens float64) {
last := lim.last
if t.Before(last) {
last = t
}
elapsed := t.Sub(last)
delta := lim.limit.tokensFromDuration(elapsed)
tokens := lim.tokens + delta
if burst := float64(lim.burst); tokens > burst {
tokens = burst
}
return t, tokens
}
3. durationFromTokens()根据令牌数量tokens计算出产生该数量的令牌需要的时长
- durationFromTokens()计算出生成N 个新的 Token 一共需要多久
func (limit Limit) durationFromTokens(tokens float64) time.Duration {
if limit <= 0 {
return InfDuration
}
seconds := tokens / float64(limit)
return time.Duration(float64(time.Second) * seconds)
}
4. tokensFromDuration()获取指定期间内产生的令牌数量
- tokensFromDuration()给定一段时长,计算这段时间一共可以生成多少个 Token
func (limit Limit) tokensFromDuration(d time.Duration) float64 {
if limit <= 0 {
return 0
}
return d.Seconds() * float64(limit)
}
Wait 阻塞获取令牌
- 在Wait阻塞消费令牌函数中,首先会封装一个返回定时器通道的函数,然后调用Limiter的wait()方法, 查看该方法:
- 首先获取到当前限速器配置的最大并发数burst 与limit限流速率,如果当前一次性申请的令牌数超过最大并发数,并且限流速率不是MaxFloat64报错
- 通过select-case监听context的Done取消,如果取消了返回异常
- 将Context取消时间,设置为等待时间
- 通过当前时间, 当前消费令牌数量,通过Context取消时间设置的等待时间,调用reserveN()进行预订
- 在reserveN()预订令牌方法中,会根据传入的等待时间计算这个时间段内是否存在或可以生产出指定数量的令牌,并且会返回生产这些令牌所需要的时间
- 如果存在或者可以生产出需要的令牌reserveN()返回的Reservation中ok为true,调用DelayFrom()方法计算获取令牌需要的延迟时间,DelayFrom()返回0说明不需要等待直接返回
- DelayFrom()如果返回大于0,执行Wait()函数中封装的返回定时器通道的函数,拿到定时器通道,通过select-case监听Context的Done取消消息和这个定时器通道,当定时器通道返回说明令牌生成完毕函数返回,Wait阻塞开始向下执行
func (lim *Limiter) WaitN(ctx context.Context, n int) (err error) {
newTimer := func(d time.Duration) (<-chan time.Time, func() bool, func()) {
timer := time.NewTimer(d)
return timer.C, timer.Stop, func() {}
}
return lim.wait(ctx, n, time.Now(), newTimer)
}
func (lim *Limiter) wait(ctx context.Context, n int, t time.Time, newTimer func(d time.Duration) (<-chan time.Time, func() bool, func())) error {
lim.mu.Lock()
burst := lim.burst
limit := lim.limit
lim.mu.Unlock()
if n > burst && limit != Inf {
return fmt.Errorf("rate: Wait(n=%d) exceeds limiter's burst %d", n, burst)
}
select {
case <-ctx.Done():
return ctx.Err()
default:
}
waitLimit := InfDuration
if deadline, ok := ctx.Deadline(); ok {
waitLimit = deadline.Sub(t)
}
r := lim.reserveN(t, n, waitLimit)
if !r.ok {
return fmt.Errorf("rate: Wait(n=%d) would exceed context deadline", n)
}
delay := r.DelayFrom(t)
if delay == 0 {
return nil
}
ch, stop, advance := newTimer(delay)
defer stop()
advance()
select {
case <-ch:
return nil
case <-ctx.Done():
r.Cancel()
return ctx.Err()
}
}
func (r *Reservation) DelayFrom(t time.Time) time.Duration {
if !r.ok {
return InfDuration
}
delay := r.timeToAct.Sub(t)
if delay < 0 {
return 0
}
return delay
}
CancelAt()取消令牌消费操作
func (r *Reservation) Cancel() {
r.CancelAt(time.Now())
return
}
func (r *Reservation) CancelAt(t time.Time) {
if !r.ok {
return
}
r.lim.mu.Lock()
defer r.lim.mu.Unlock()
if r.lim.limit == Inf || r.tokens == 0 || r.timeToAct.Before(t) {
return
}
restoreTokens := float64(r.tokens) - r.limit.tokensFromDuration(r.lim.lastEvent.Sub(r.timeToAct))
if restoreTokens <= 0 {
return
}
t, tokens := r.lim.advance(t)
tokens += restoreTokens
if burst := float64(r.lim.burst); tokens > burst {
tokens = burst
}
r.lim.last = t
r.lim.tokens = tokens
if r.timeToAct == r.lim.lastEvent {
prevEvent := r.timeToAct.Add(r.limit.durationFromTokens(float64(-r.tokens)))
if !prevEvent.Before(t) {
r.lim.lastEvent = prevEvent
}
}
}
- 可以调用Cancel()取消令牌消费操作,该函数中会调用CancelAt(),首先要了解Reservation中的几个字段
- r.tokens指的是本次消费的token数,
- r.timeToAcr指的是Token桶可以满足本次消费数目的时刻,也就是消费的时刻+等待的时长
- r.lim.lastEvent指的是最近一次消费的timeToAct的值
- 在CancelAt()中最重要的逻辑:通过r.limit.tokensFromDuration方法得出从该次消费到当前时间一共又消费了多少Token数目,然后"r.tokens本次消费的令牌数" 减去 "从该次消费到当前时间一共又消费的令牌数"得出要归还的令牌
restoreTokens := float64(r.tokens) - r.limit.tokensFromDuration(r.lim.lastEvent.Sub(r.timeToAct)),
- 然后更新Reservation中的上次token更新时间,剩余令牌数等信息
三. 总结
复习限流算法
- 先说一下几个限流相关算法的优缺点
- 计数器: 只需要维护一个计数器变量,实现简单,性能消耗较小,缺点无法控制速率,无法解决瞬时高并发问题
- 令牌桶:
- 固定速率,向桶中添加令牌,桶容量是有限的,接收到请求后首先获取桶中的令牌进行消费
- 缺点: 需要维护令牌生成速率与桶容量实现稍微有点复杂,通过桶容量一定程度上解决了瞬时并发问题,但是没有彻底解决
- 漏桶
- 可以以任意速率流入水滴到漏桶中,但是按照固定速率流出水滴, 如果桶是空的,则不需流出水滴,;如果流入水滴超出了桶的容量,则流入的水滴溢出了执行服务降级
- 令牌桶是按照固定速率往桶中添加令牌,请求是否被处理需要看桶中令牌是否足够,当令牌数减为零时则拒绝新的请求;漏桶则是按照常量固定速率流出请求,请求到达后端,流入请求速率任意请求有客户端发送到桶中,当流入的请求数累积到漏桶容量时,则新流入的请求被拒绝
- 缺点: 与令牌桶相同
- 滑动窗口
- 滑动窗口出现的原因: 假设通过计数器实现限流,限制每秒钟最高允许10个请求通过,每请求一次计数器+1,当请求超过10,并且与第一次请求的时间间隔不超过一秒钟,说明请求过多,服务熔断降级,如果与第一次请求间隔超过一秒钟,说明还在范围内重置计数器(思考问题临界问题:假设在第1秒时接收到9个请求,都在执行中,到2秒计数器重置进入新的计算阶段又进来9个请求,当前实际就承载了18个请求,超过阈值)
- 滑动窗口算法计数器: 解决传统计数器临界问题: 假设每分钟允许向后台请求60次,可以将这60次请求分为6份,每秒钟允许请求10次,每个时间段都有自己的计数器,记录这个时间段内发生的请求数量,通过滑动进行判断,每过一个时间段,就把最早的时间段和它的计数器删掉,然后加入一个新的时间段和计数器,进而解决临界问题
time/rate 总结
- time/rate 基于令牌桶算法实现的限流组件,允许一定程度的突发,同时保证了请求的平均速率,内部基于锁,channel,保证了并发安全,牺牲了一点性能但是保证了令牌的精确性
- 了解time/rate限流首先要了解Limiter与Reservation两个结构体,在调用NewLimiter()函数创建限流器时会返回一个Limiter 结构体变量,内部有一个limit 属性表示每秒处理的频率也就是限流速率, burst 最大并发数,mu 锁, tokens 当前令牌桶中可用的令牌数…
- 在实现限流时底层都会调用到一个reserveN()函数,会根据传入的等待时间,计算这段时间内能否生成需要的令牌数,封装Reservation,在Reservation 中存在,tokens本次消费的令牌数,ok 到截止时间是否能够生成指定的令牌数属性,timeToAct 获取指定令牌需要等待的时间属性
- 消费令牌原理:
- rate中提供了Allow()消费一个令牌,Wait()阻塞等待消费一个令牌, Reserve()预定一个令牌,内部实际都会调用到调用reserveN和advance方法,reserveN()用于判断在指定时间内是否有足够的令牌
- 首先判断当前消费的令牌数是否超过了最大限制,如果超过了,直接报错,判断limit限流速率如果不等于MaxFloat64并且不等于0,调用advance()计算当前可以使用的令牌数量,也就是(上次剩余令牌数+上次消费令牌时间到当前时间所生成的令牌,如果大于最大并发限制,的更新为最大并发限制)
- 如果advance()拿到当前可消费的令牌数量小于0,说明超过并发限制,调用durationFromTokens()计算等待生成令牌时间
封装Reservation,如果当前要消费的令牌数量小于允许可消费的令牌数量则Reservation中的ok为true表示允许消费,否则为false,最后解锁,返回预约结果
- Wait 阻塞获取令牌原理,在Wait阻塞消费令牌函数中,首先会封装一个返回定时器通道的函数,然后调用Limiter的wait()方法,
- 在wait()方法中,会通过select-case监听context的Done取消,如果取消了返回异常,并获取context的取消时间,作为等待时间调用reserveN()进行预订
- 在reserveN()预订令牌方法中,会根据传入的等待时间计算这个时间段内是否存在或可以生产出指定数量的令牌,并且会返回生产这些令牌所需要的时间
- 如果存在或指定等待时间内可以生产出需要的令牌reserveN()返回的Reservation中ok为true,调用DelayFrom()方法计算获取令牌需要的等待时间,DelayFrom()返回0说明不需要等待直接返回,大于0,执行Wait()函数中封装的返回定时器通道的函数,拿到定时器通道,通过select-case监听Context的Done取消消息和这个定时器通道,当定时器通道返回说明令牌生成完毕函数返回,Wait阻塞开始向下执行
- Cancel()取消令牌消费操作: Cancel()函数内部会调用CancelAt(),该函数中最重要的逻辑:通过r.limit.tokensFromDuration方法得出从该次消费到当前时间一共又消费了多少Token数目,然后"r.tokens本次消费的令牌数" 减去 "从该次消费到当前时间一共又消费的令牌数"得出要归还的令牌,然后更新Reservation中的上次token更新时间,剩余令牌数等信息