上图可以看出 client-go 用到了 workqueue 队列 来处理 从 DeltaFIFO pop 出来的内容,workqueue 队列用到了限流队列(微服务中常用的技术,防止性能过载,从而导致任务处理失败)。

在分析workqueue前,需要了解下实现限流队列的限流器。限流器有多种实现方式,client-go用了其中一种,client-go用的限流器是 Golang 标准库限流器(Golang 的 timer/rate)。

本篇是关于 Golang 标准库限流器。

Golang 标准库限流器通过令牌桶实现。令牌桶可以想象有一个固定大小的桶,通过有取有放,实现了限流目的。

放:系统会以恒定速率向桶中放 Token,桶满则暂时不放。

取:用户则从桶中取 Token,如果有剩余 Token 就可以一直取。如果没有剩余 Token,则需要等到系统中被放置了 Token 才行。

直接按上面的实现,效率太低了,不仅要维护一个定时放token的定时器,还要维护一个token队列,消耗不必要的内存和算力。在 Golang 的 timer/rate 中的实现 是通过 lazyload 的方式,每次消费之前才根据时间差更新 Token 数目(是计算得到的)。

下面进入代码。内容主要是两个结构体,Limiter struct 和 Reservation struct。两个方法,reserve 预留方法 和 Token 的归还方法。

reserve 方法的大致流程:

  1. if lim.limit == Inf 如果最大时间率是无限大的,那么直接返回 Reservation struct, ok=true,预定执行时间是立刻。相当于没有限流器,限流器功能disable。
  2. lim.advance(now) 重新计算桶里的token数目,就是通过计算Limiter结构体的last属性减去现在的时间,算出这段时间流逝中应该往桶里加多少token,加上旧的token数目(Limiter结构体的tokens属性)就是now的token数目
  3. tokens -= float64(n) now的token数目减去reserve方法的入参n,就是经过reserve消费后的token数目
  4. 更新last时间为将now时间,返回结构体Reservation

CancelAt 表示预订持有者不会执行预订的操作,并考虑到可能已经进行了其他预订,并尽可能地逆转此预订对速率限制的影响。

CancelAt 方法的大致流程:

  1. 四种直接返回的情况: !r.ok r.lim.limit == Inf r.tokens == 0 r.timeToAct.Before(now)
  2. restoreTokens := float64(r.tokens) - r.limit.tokensFromDuration(r.lim.lastEvent.Sub(r.timeToAct)) 算出要归还的token数目
  3. r.lim.advance(now) lazyload 方式算出当前时间的token数目
  4. tokens += restoreTokens 算出归还后的token数目

欢迎订阅微信公众号“后端云”!