golang--ratelimit令牌桶原理分析

什么是限流

   限流对某一时间窗口内高于系统承载的请求进行限制,通过限速来保护系统,一旦达到限制速率则可拒绝服务,等待。常见调用平台及服务,比如微信发消费券服务每秒500qps,万一我们超过请求频次,就会发生意想不到的业务问题,踩过坑的小伙伴深有体会

限流方式-令牌桶

  常见限流方法:计数器、令牌桶、漏桶。这里我们就只展开对令牌桶展开讨论。令牌桶是以一定的速率往令牌桶中生产令牌,桶满则丢弃,请求request过来时,先从桶中获取一个令牌,成功获取令牌就处理请求,失败就丢弃请求

golang--ratelimit令牌桶原理分析_第1张图片

令牌桶实现原理

  1. 单位时间按照一定速率匀速的生产 token放入桶内,直到达到桶容量上限
  2. 处理请求,每次尝试获取一个或多个令牌
  3. 如果拿到则处理请求,失败则拒绝请求

   juju/ratelimit令牌桶限流器在golang开发中使用比较多,而且自己在项目中刚好需要使用到、今天就从它进行展开了解,学习使用和实现的原理

   juju/ratelimit中主要有三个文件,ratelimit.go, ratelimit_test.go、reader.go

golang--ratelimit令牌桶原理分析_第2张图片

        先看ratelimit定义令牌桶的结构Bucket,简单看Bucket中属性的值代表的意义

type Bucket struct {
      //Clock是个接口,由realClock根据标准时间函数实现
    clock Clock

    //令牌桶首次创建的时间
    startTime time.Time

    //桶容量
    capacity int64

    //每个tick向桶内添加的令牌数
    quantum int64

    //每个刻度的间隔
    fillInterval time.Duration

    //锁
    mu sync.Mutex

    //相关的tick有效令牌数,消费者等待令牌时为负数
    availableTokens int64

    //令牌中的数量保持最新的刻度
    latestTick int64
}

重点:tick计算方式

   整个ratelimit对于ticket使用和更新贯穿整个令牌桶流程,无论从注释和代码中都有tick的使用:tick = (当前时间 - 初始时间)/ 时间间隔(fillInterval )

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

创建令牌桶的方式

// 指定时间间隔、容量大小的令牌桶、每个tick添加的令牌数、 clock的类型是Clock
func NewBucket(fillInterval time.Duration, capacity int64) *Bucket
// 指定时间间隔、容量大小、每个tick添加的令牌数 quantum 
func NewBucketWithQuantum(fillInterval time.Duration, capacity, quantum int64) *Bucket
// 创建填充速度为指定速率和容量大小的令牌桶
// NewBucketWithRate(0.5, 100) 表示每秒填充50个令牌, fillInterval 时间间隔为1
func NewBucketWithRate(rate float64, capacity int64) *Bucket

   其实这三种方式调用的都是下面函数的二次封装

func NewBucketWithQuantumAndClock(fillInterval time.Duration, capacity, quantum int64, clock Clock) *Bucket

领取令牌

   下面到了获取令牌的时候了TakeAvailable() 获取令牌,获取令牌会使用sync.Mutx 进行加锁,执行完后解锁。

   获取令牌时,调整令牌桶的的代码如下

func (tb *Bucket) adjustavailableTokens(tick int64) {
   if tb.availableTokens >= tb.capacity {
      return
   }
   tb.availableTokens += (tick - tb.latestTick) * tb.quantum
   if tb.availableTokens > tb.capacity {
      tb.availableTokens = tb.capacity
   }
   tb.latestTick = tick
   return
}
  1. 如果当前可用令牌数大于等于当前容量,就不更新可用令牌数量
  2. tb.availableTokens += (tick - tb.latestTick) * tb.quantum 可用令牌数 = 可用令牌数 + (当前时间的tick - 最后更新的tick) * 每个tick令牌添加数
  3. 如果availableTokens 大于容量,因为不能超过容量,所以可用令牌数设置为容量值
  4. 更新latestTick的值
  5. 获取令牌的方式也是将总数 -1

   上面更新令牌数的方式没有通过其他的goroutine等方式去更新,而是转换为了挺有意思的数学问题,简单明了直接。当然juju/ratelimit还有其他一些获取令牌桶信息的方法,这里就不展开讨论了。具体我们可以去看源码,整体代码量还是可以接受的,哈哈

 

你可能感兴趣的:(golang-三方库,golang,开发语言,后端)