golang 令牌桶限速器实现(ratelimit)

首先科普一下常用时间换算:

1000 毫秒(ms) = 1秒
1,000,000 微秒(μs) = 1秒 
1,000,000,000 纳秒(ns) = 1秒 

这个应该不需要解释。
首先看一下定义数据结构:

type Bucket struct {
    startTime    time.Time
    capacity     int64
    quantum      int64
    fillInterval time.Duration
    mu sync.Mutex

    avail     int64
    availTick int64
}

上面的avail就是可用的令牌个数,availTick是已经走过的时间tick,这个后面会讲到。限速,就是要控制每秒的速度,看一下速度是怎么设定的10的九次方(纳秒)*因子除以时间间隔,

func (tb *Bucket) Rate() float64 {
    return 1e9 * float64(tb.quantum) / float64(tb.fillInterval)
}

测试一下“速度”

func isCloseTo(x, y, tolerance float64) bool {
    return math.Abs(x-y)/y < tolerance
}

tb = ratelimit.NewBucketWithQuantum(100*time.Millisecond, 1, 5)
println(isCloseTo(tb.Rate(), 50, 0.00001))

当我们需要使用限速的时候就是去查询可用的令牌,通过方法

func (tb *Bucket) available(now time.Time) int64 {
    tb.mu.Lock()
    defer tb.mu.Unlock()
    tb.adjust(now)
    return tb.avail
}

这个方法通过adjust调整,

func (tb *Bucket) adjust(now time.Time) (currentTick int64) {
    currentTick = int64(now.Sub(tb.startTime) / tb.fillInterval)

    if tb.avail >= tb.capacity {
        return
    }
    tb.avail += (currentTick - tb.availTick) * tb.quantum
    if tb.avail > tb.capacity {
        tb.avail = tb.capacity
    }
    tb.availTick = currentTick
    return
}

我们看看是怎么调整的,拿当前时间减去初始时间除以时间间隔获取tick个数,那这个tick数减去之前已经分配tick个数,就是增量的时间Tick,再拿这个增量的Tick*因子就算出要增加的令牌个数,这样就调整了avail的个数了,并且更新availTick个数,当然不能超过容量,所以才有了tb.avail = tb.capacity。设计的简单可靠,不需要开启单独协程定时的往这个bucket里面加入令牌。
查到可用的令牌,当然你就可以获取令牌了,通过toke方法,

func (tb *Bucket) take(now time.Time, count int64, maxWait time.Duration) (time.Duration, bool) {
    if count <= 0 {
        return 0, true
    }
    tb.mu.Lock()
    defer tb.mu.Unlock()

    currentTick := tb.adjust(now)
    avail := tb.avail - count
    if avail >= 0 {
        tb.avail = avail
        return 0, true
    }
    // Round up the missing tokens to the nearest multiple
    // of quantum - the tokens won't be available until
    // that tick.
    endTick := currentTick + (-avail+tb.quantum-1)/tb.quantum
    endTime := tb.startTime.Add(time.Duration(endTick) * tb.fillInterval)
    waitTime := endTime.Sub(now)
    if waitTime > maxWait {
        return 0, false
    }
    tb.avail = avail
    return waitTime, true
}

这个里面不仅涉及到令牌获取,获取后tb.avail - count,而且还可以估算等待时间,如果超过现有令牌,可以预估等待时间waitTime,这样不仅可以获取,还可以通过Wait方法等待,如果成功返回0, true。

func (tb *Bucket) Wait(count int64) {
    if d := tb.Take(count); d > 0 {
        time.Sleep(d)
    }
}

等待,直到令牌数符合要求。那个整个限速器基本功能就具备了,还补充两个方法,

func (tb *Bucket) takeAvailable(now time.Time, count int64) int64 {
    if count <= 0 {
        return 0
    }
    tb.mu.Lock()
    defer tb.mu.Unlock()

    tb.adjust(now)
    if tb.avail <= 0 {
        return 0
    }
    if count > tb.avail {
        count = tb.avail
    }
    tb.avail -= count
    return count
}

这个方法会安全的拿到可用的令牌,如果获取令牌超过的话,会获取并返回当前现有的令牌。当然,如果你已经知道速度,就可以创建一个已知rate的令牌桶了

func NewBucketWithRate(rate float64, capacity int64) *Bucket {
    for quantum := int64(1); quantum < 1<<50; quantum = nextQuantum(quantum) {
        fillInterval := time.Duration(1e9 * float64(quantum) / rate)
        if fillInterval <= 0 {
            continue
        }
        tb := NewBucketWithQuantum(fillInterval, capacity, quantum)
        if diff := math.Abs(tb.Rate() - rate); diff/rate <= rateMargin {
            return tb
        }
    }
    panic("cannot find suitable quantum for " + strconv.FormatFloat(rate, 'g', -1, 64))
}

这样完整的限速器完毕

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

柳清风09

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

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

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

打赏作者

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

抵扣说明:

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

余额充值