关于golang:不得不了解系列之限流

5次阅读

共计 12732 个字符,预计需要花费 32 分钟才能阅读完成。

限流简介

当初说到高可用零碎,都会说到高可用的爱护伎俩:缓存、降级和限流,本博文就次要说说限流。限流是流量限速(Rate Limit)的简称,是指只容许指定的事件进入零碎,超过的局部将被拒绝服务、排队或期待、降级等解决。对于 server 服务而言,限流为了保障一部分的申请流量能够失去失常的响应,总好过全副的申请都不能失去响应,甚至导致系统雪崩。限流与熔断常常被人弄混,博主认为它们最大的区别在于限流次要在 server 实现,而熔断次要在 client 实现,当然了,一个服务既能够充当 server 也能够充当 client,这也是让限流与熔断同时存在一个服务中,这两个概念才容易被混同。

那为什么须要限流呢?很多人第一反馈就是服务扛不住了所以须要限流。这是不全面的说法,博主认为限流是因为资源的稀缺或出于平安防备的目标,采取的自我爱护的措施。限流能够保障应用无限的资源提供最大化的服务能力,依照预期流量提供服务,超过的局部将会拒绝服务、排队或期待、降级等解决。

当初的系统对限流的反对各有不同,然而存在一些规范。在 HTTP RFC 6585 规范中规定了『429 Too Many Requests』,429 状态码示意用户在给定工夫内发送了太多的申请,须要进行限流(“速率限度”),同时蕴含一个 Retry-After 响应头用于通知客户端多长时间后能够再次申请服务。

HTTP/1.1 429 Too Many Requests
Content-Type: text/html
Retry-After: 3600


  
     <title>Too Many Requests</title>
  
  
     <h1>Too Many Requests</h1>
     <p>I only allow 50 requests per hour to this Web site per
        logged in user.  Try again soon.</p>
  

很多利用框架同样集成了,限流性能并且在返回的 Header 中给出明确的限流标识。

  • X-Rate-Limit-Limit:同一个时间段所容许的申请的最大数目;
  • X-Rate-Limit-Remaining:在以后时间段内残余的申请的数量;
  • X-Rate-Limit-Reset:为了失去最大申请数所期待的秒数。

这是通过响应头通知调用方服务端的限流频次是怎么的,保障后端的接口拜访下限,客户端也能够依据响应的 Header 调整申请。

限流分类

限流 ,拆分来看,就两个字 就是动词限度,很好了解。然而 在不同的场景之下就是不同资源或指标,多样性就在 中体现。在网络流量中能够是字节流,在数据库中能够是 TPS,在 API 中能够是 QPS 亦能够是并发申请数,在商品中还能够是库存数 … … 然而不论是哪一种『流』,这个流必须能够 被量化,能够被度量,能够被察看到、能够统计进去
咱们把限流的分类基于不同的形式分为不同的类别,如下图。

因为篇幅无限,本文只会筛选几个常见的类型分类进行阐明。

限流粒度分类

依据限流的粒度分类:

  • 单机限流
  • 分布式限流

现状的零碎基本上都是分布式架构,单机的模式曾经很少了,这里说的单机限流更加精确一点的说法是单服务节点限流。单机限流是指申请进入到某一个服务节点后超过了限流阈值,服务节点采取了一种限流保护措施。

分布式限流广义的说法是在接入层实现多节点合并限流,比方 NGINX+redis,分布式网关等,狭义的分布式限流是多个节点(能够为不同服务节点)有机整合,造成整体的限流服务。

单机限流避免流量压垮服务节点,不足对整体流量的感知。分布式限流适宜做细粒度不同的限流管制,能够依据场景不同匹配不同的限流规定。与单机限流最大的区别,分布式限流须要中心化存储,常见的应用 redis 实现。引入了中心化存储,就须要解决以下问题:

  • 数据一致性

    在限流模式中现实的模式为工夫点一致性。工夫点一致性的定义中要求所有数据组件的数据在任意时刻都是完全一致的,然而一般来说信息流传的速度最大是光速,其实并不能达到任意时刻统一,总有肯定的工夫不统一,对于咱们 CAP 中的一致性来说只有达到读取到最新数据即可,达到这种状况并不需要严格的任意工夫统一。这只能是实践当中的一致性模型,能够在限流中达到线性一致性即可。

  • 工夫一致性

    这里的工夫一致性与上述的工夫点一致性不一样,这里就是指各个服务节点的工夫一致性。一个集群有 3 台机器,然而在某一个 A / B 机器的工夫为Tue Dec 3 16:29:28 CST 2019,C 为Tue Dec 3 16:29:28 CST 2019,那么它们的工夫就不统一。那么应用 ntpdate 进行同步也会存在肯定的误差,对于工夫窗口敏感的算法就是误差点。

  • 超时

    在分布式系统中就须要网络进行通信,会存在网络抖动问题,或者分布式限流中间件压力过大导致响应变慢,甚至是超时工夫阈值设置不合理,导致应用服务节点超时了,此时是放行流量还是回绝流量?

  • 性能与可靠性

    分布式限流中间件的资源总是无限的,甚至可能是单点的(写入单点),性能存在下限。如果分布式限流中间件不可用时候如何进化为单机限流模式也是一个很好的降级计划。

限流对象类型分类

依照对象类型分类:

  • 基于申请限流
  • 基于资源限流

基于申请限流,个别的实现形式有 限度总量 限度 QPS。限度总量就是限度某个指标的下限,比方抢购某一个商品,放量是 10w,那么最多只能卖出 10w 件。微信的抢红包,群里发一个红包拆分为 10 个,那么最多只能有 10 人能够抢到,第十一个人关上就会显示『手慢了,红包派完了』。

限度 QPS,也是咱们常说的限流形式,只有在接口层级进行,某一个接口只容许 1 秒只能拜访 100 次,那么它的峰值 QPS 只能为 100。限度 QPS 的形式最难的点就是如何预估阈值,如何定位阈值,下文中会说到。

基于资源限流是基于服务资源的应用状况进行限度,须要定位到服务的要害资源有哪些,并对其进行限度,如限度 TCP 连接数、线程数、内存使用量等。限度资源更能无效地反映出服务以后地清理,但与限度 QPS 相似,面临着如何确认资源的阈值为多少。这个阈值须要一直地调优,不停地实际才能够失去一个较为称心地值。

限流算法分类

不论是依照什么维度,基于什么形式的分类,其限流的底层均是须要算法来实现。上面介绍实现常见的限流算法:

  • 计数器
  • 令牌桶算法
  • 漏桶算法

计数器

固定窗口计数器

计数限流是最为简略的限流算法,日常开发中,咱们说的限流很多都是说固定窗口计数限流算法,比方某一个接口或服务 1s 最多只能接管 1000 个申请,那么咱们就会设置其限流为 1000QPS。该算法的实现思路非常简单,保护一个固定单位工夫内的计数器,如果检测到单位工夫曾经过来就重置计数器为零。

其操作步骤:

  1. 工夫线划分为多个独立且固定大小窗口;
  2. 落在每一个工夫窗口内的申请就将计数器加 1;
  3. 如果计数器超过了限流阈值,则后续落在该窗口的申请都会被回绝。但工夫达到下一个工夫窗口时,计数器会被重置为 0。

上面实现一个简略的代码。

package limit

import (
   "sync/atomic"
   "time"
)

type Counter struct {
   Count       uint64   // 初始计数器
   Limit       uint64  // 单位工夫窗口最大申请频次
   Interval    int64   // 单位 ms
   RefreshTime int64   // 工夫窗口
}

func NewCounter(count, limit uint64, interval, rt int64) *Counter {
   return &amp;Counter{
      Count:       count,
      Limit:       limit,
      Interval:    interval,
      RefreshTime: rt,
   }
}

func (c *Counter) RateLimit() bool {now := time.Now().UnixNano() / 1e6
   if now &lt; (c.RefreshTime + c.Interval) {atomic.AddUint64(&amp;c.Count, 1)
      return c.Count &lt;= c.Limit
   } else {
      c.RefreshTime = now
      atomic.AddUint64(&amp;c.Count, -c.Count)
      return true
   }
}

测试代码:

package limit

import (
   "fmt"
   "testing"
   "time"
)

func Test_Counter(t *testing.T) {counter := NewCounter(0, 5, 100, time.Now().Unix())
   for i := 0; i &lt; 10; i++ {go func(i int) {
         for k := 0; k &lt;= 10; k++ {fmt.Println(counter.RateLimit())
            if k%3 == 0 {time.Sleep(102 * time.Millisecond)
            }
         }
      }(i)
   }
   time.Sleep(10 * time.Second)
}

看了下面的逻辑,有没有感觉固定窗口计数器很简略,对,就是这么简略,这就是它的一个长处实现简略。同时也存在两个比较严重缺点。试想一下,固定工夫窗口 1s 限流阈值为 100,然而前 100ms,曾经申请来了 99 个,那么后续的 900ms 只能通过一个了,就是一个缺点,基本上没有应答突发流量的能力。第二个缺点,在 00:00:00 这个工夫窗口的后 500ms,申请通过了 100 个,在 00:00:01 这个工夫窗口的前 500ms 还有 100 个申请通过,对于服务来说相当于 1 秒内申请量达到了限流阈值的 2 倍。

滑动窗口计数器

滑动工夫窗口算法是对固定工夫窗口算法的一种改良,这词被公众所知切实 TCP 的流量管制中。固定窗口计数器能够说是滑动窗口计数器的一种特例,滑动窗口的操作步骤:

  1. 将单位工夫划分为多个区间,个别都是均分为多个小的时间段;
  2. 每一个区间内都有一个计数器,有一个申请落在该区间内,则该区间内的计数器就会加一;
  3. 每过一个时间段,工夫窗口就会往右滑动一格,摈弃最老的一个区间,并纳入新的一个区间;
  4. 计算整个工夫窗口内的申请总数时会累加所有的工夫片段内的计数器,计数总和超过了限度数量,则本窗口内所有的申请都被抛弃。

工夫窗口划分的越细,并且依照工夫 ” 滑动 ”,这种算法防止了固定窗口计数器呈现的上述两个问题。毛病是工夫区间的精度越高,算法所需的空间容量就越大。

常见的实现形式次要有基于 redis zset 的形式和循环队列实现。基于 redis zset 可将 Key 为限流标识 ID,Value 放弃惟一,能够用 UUID 生成,Score 也记为同一时间戳,最好是纳秒级的。应用 redis 提供的 ZADD、EXPIRE、ZCOUNT 和 zremrangebyscore 来实现,并同时留神开启 Pipeline 来尽可能晋升性能。实现很简略,然而毛病就是 zset 的数据结构会越来越大。

漏桶算法

漏桶算法是水先进入到漏桶里,漏桶再以肯定的速率出水,当流入水的数量大于流出水时,多余的水间接溢出。把水换成申请来看,漏桶相当于服务器队列,但申请量大于限流阈值时,多进去的申请就会被拒绝服务。漏桶算法应用队列实现,能够以固定的速率管制流量的访问速度,能够做到流量的“平坦化”解决。

大家能够通过网上最风行的一张图来了解。

漏桶算法实现步骤:

  1. 将每个申请放入固定大小的队列进行存储;
  2. 队列以固定速率向外流出申请,如果队列为空则进行流出;
  3. 如队列满了则多余的申请会被间接回绝·

漏桶算法有一个显著的缺点:当短时间内有大量的突发申请时,即便服务器负载不高,每个申请也都得在队列中期待一段时间能力被响应。

令牌桶算法

令牌桶算法的原理是零碎会以一个恒定的速率往桶里放入令牌,而如果申请须要被解决,则须要先从桶里获取一个令牌,当桶里没有令牌可取时,则拒绝服务。从原理上看,令牌桶算法和漏桶算法是相同的,前者为“进”,后者为“出”。
漏桶算法与令牌桶算法除了“方向”上的不同还有一个更加次要的区别:令牌桶算法限度的是均匀流入速率(容许突发申请,只有有足够的令牌,反对一次拿多个令牌),并容许肯定水平突发流量;

令牌桶算法的实现步骤:

  1. 令牌以固定速率生成并放入到令牌桶中;
  2. 如果令牌桶满了则多余的令牌会间接抛弃,当申请达到时,会尝试从令牌桶中取令牌,取到了令牌的申请能够执行;
  3. 如果桶空了,则回绝该申请。

四种策略该如何抉择?

  • 固定窗口:实现简略,然而过于粗犷,除非情况紧急,为了能疾速止损眼前的问题能够作为长期应急的计划。
  • 滑动窗口:限流算法简略易实现,能够应答有大量突增流量场景。
  • 漏桶:对于流量相对平均有很强的要求,资源的利用率上不是极致,但其宽进严出模式,爱护零碎的同时还留有局部余量,是一个通用性计划。
  • 令牌桶:零碎常常有突增流量,并尽可能的压迫服务的性能。

怎么做限流?

不管应用上述的哪一种分类或者实现形式,零碎都会面临一个独特的问题:如何确认限流阈值。
有人团队依据教训先设定一个小的阈值,后续缓缓进行调整;有的团队是通过进行压力测试后总结进去。这种形式的问题在于压测模型与线上环境不肯定统一,接口的单压不能反馈整个零碎的状态,全链路压测又难以实在反馈理论流量场景流量比例。再换一个思路是通过压测 + 各利用监控数据。依据零碎峰值的 QPS 与系统资源应用状况,进行等水位放大预估限流阈值,问题在于零碎性能拐点未知,单纯的预测不肯定精确甚至极大偏离实在场景。
正如《Overload Control for Scaling WeChat Microservices》所说,在具备简单依赖关系的零碎中,对特定服务的进行过载管制可能对整个零碎无害或者服务的实现有缺点。
心愿后续能够呈现一个更加 AI 的运行反馈主动设置限流阈值的零碎,能够依据以后 QPS、资源状态、RT 状况等多种关联数据动静地进行过载爱护。

不论是哪一种形式给出的限流阈值,零碎都应该关注以下几点:

  1. 运行指标状态,比方以后服务的 QPS、机器资源应用状况、数据库的连接数、线程的并发数等;
  2. 资源间的调用关系,内部链路申请、外部服务之间的关联、服务之间的强弱依赖等;
  3. 管制形式,达到限流后对后续的申请间接回绝、疾速失败、排队期待等解决形式

go 限流类库应用

限流的类库有很多,不同语言的有不同的类库,如大 Java 的有 concurrency-limits、Sentinel、Guava 等,这些类库都有很多的剖析和应用形式了,本文次要介绍 Golang 的限流类库就是 Golang 的扩大库:https://github.com/golang/tim…。能够进去语言类库的代码都值得去研读一番,学习过 Java 的同学是否对 AQS 的设计之精妙而感叹呢!
time/rate 也有其精妙的局部,上面开始进入类库学习阶段。

github.com/golang/time/rate

进行源码剖析前的,最应该做的是理解类库的应用形式、应用场景和 API。对业务有了初步的理解,浏览代码就能够事倍功半。因为篇幅无限后续的博文在对多个限流类库源码做剖析。
类库的 API 文档:https://godoc.org/golang.org/…。
time/rate 类库是基于令牌桶算法实现的限流性能。后面说令牌桶算法的原理是零碎会以一个恒定的速率往桶里放入令牌,那么桶就有一个固定的大小,往桶中放入令牌的速率也是恒定的,并且容许突发流量。查看文档发现一个函数:

func NewLimiter(r Limit, b int) *Limiter

newLimiter 返回一个新的限制器,它容许事件的速率达到 r,并容许最多突发 b 个令牌。也就是说 Limter 限度工夫的产生频率,但这个桶一开始容量就为 b,并且装满 b 个令牌(令牌池中最多有 b 个令牌,所以一次最多只能容许 b 个事件产生,一个事件破费掉一个令牌),而后每一个单位工夫距离(默认 1s)往桶里放入 r 个令牌。

limter := rate.NewLimiter(10, 5)

下面的例子示意,令牌桶的容量为 5,并且每一秒中就往桶里放入 10 个令牌。仔细的读者都会发现函数 NewLimiter 第一个参数是 Limit 类型,能够看源码就会发现 Limit 实际上就是 float64 的别名。

// Limit defines the maximum frequency of some events.
// Limit is represented as number of events per second.
// A zero Limit allows no events.
type Limit float64

限流器还能够指定往桶里放入令牌的工夫距离,实现形式如下:

limter := rate.NewLimiter(rate.Every(100*time.Millisecond), 5)

这两个例子的成果是一样的,应用第一种形式不会呈现在每一秒距离一下子放入 10 个令牌,也是平均扩散在 100ms 的距离放入令牌。
rate.Limiter 提供了三类办法用来限速:

  • Allow/AllowN
  • Wait/WaitN
  • Reserve/ReserveN

上面比照这三类限流形式的应用形式和实用场景。先看第一类办法:

func (lim *Limiter) Allow() bool
func (lim *Limiter) AllowN(now time.Time, n int) bool

Allow 是 AllowN(time.Now(), 1)的简化办法。那么重点就在办法 AllowN 上了,API 的解释有点形象,说得云里雾里的,能够看看上面的 API 文档解释:

AllowN reports whether n events may happen at time now. 
Use this method if you intend to drop / skip events that exceed the rate limit. 
Otherwise use Reserve or Wait.

实际上就是为了说,办法 AllowN 在指定的工夫时是否能够从令牌桶中取出 N 个令牌。也就意味着能够限定 N 个事件是否能够在指定的工夫同时产生。这个两个办法是无阻塞,也就是说一旦不满足,就会跳过,不会期待令牌数量足够才执行。也就是文档中的第二行解释,如果打算失落或跳过超出速率限度的工夫,那么久请应用该办法。比方应用之前实例化好的限流器,在某一个时刻,服务器同时收到超过了 8 个申请,如果令牌桶内令牌小于 8 个,那么这 8 个申请就会被抛弃。
一个小示例:

func AllowDemo() {limter := rate.NewLimiter(rate.Every(200*time.Millisecond), 5)
   i := 0
   for {
      i++
      if limter.Allow() {fmt.Println(i, "====Allow======", time.Now())
      } else {fmt.Println(i, "====Disallow======", time.Now())
      }
      time.Sleep(80 * time.Millisecond)
      if i == 15 {return}
   }
}

执行后果:

1 ====Allow====== 2019-12-14 15:54:09.9852178 +0800 CST m=+0.005998001
2 ====Allow====== 2019-12-14 15:54:10.1012231 +0800 CST m=+0.122003301
3 ====Allow====== 2019-12-14 15:54:10.1823056 +0800 CST m=+0.203085801
4 ====Allow====== 2019-12-14 15:54:10.263238 +0800 CST m=+0.284018201
5 ====Allow====== 2019-12-14 15:54:10.344224 +0800 CST m=+0.365004201
6 ====Allow====== 2019-12-14 15:54:10.4242458 +0800 CST m=+0.445026001
7 ====Allow====== 2019-12-14 15:54:10.5043101 +0800 CST m=+0.525090301
8 ====Allow====== 2019-12-14 15:54:10.5852232 +0800 CST m=+0.606003401
9 ====Disallow====== 2019-12-14 15:54:10.6662181 +0800 CST m=+0.686998301
10 ====Disallow====== 2019-12-14 15:54:10.7462189 +0800 CST m=+0.766999101
11 ====Allow====== 2019-12-14 15:54:10.8272182 +0800 CST m=+0.847998401
12 ====Disallow====== 2019-12-14 15:54:10.9072192 +0800 CST m=+0.927999401
13 ====Allow====== 2019-12-14 15:54:10.9872224 +0800 CST m=+1.008002601
14 ====Disallow====== 2019-12-14 15:54:11.0672253 +0800 CST m=+1.088005501
15 ====Disallow====== 2019-12-14 15:54:11.1472946 +0800 CST m=+1.168074801

第二类办法:因为 ReserveN 比较复杂,第二类先说 WaitN。

func (lim *Limiter) Wait(ctx context.Context) (err error)
func (lim *Limiter) WaitN(ctx context.Context, n int) (err error)

相似 Wait 是 WaitN(ctx, 1)的简化办法。与 AllowN 不同的是 WaitN 会阻塞,如果令牌桶内的令牌数有余 N 个,WaitN 会阻塞一段时间,阻塞工夫的时长能够用第一个参数 ctx 进行设置,把 context 实例为 context.WithDeadline 或 context.WithTimeout 进行制订阻塞的时长。

func WaitNDemo() {limter := rate.NewLimiter(10, 5)
   i := 0
   for {
      i++
      ctx, canle := context.WithTimeout(context.Background(), 400*time.Millisecond)
      if i == 6 {
         // 勾销执行
         canle()}
      err := limter.WaitN(ctx, 4)

      if err != nil {fmt.Println(err)
         continue
      }
      fmt.Println(i, ", 执行:", time.Now())
      if i == 10 {return}
   }
}

执行后果:

1 , 执行:2019-12-14 15:45:15.538539 +0800 CST m=+0.011023401
2 , 执行:2019-12-14 15:45:15.8395195 +0800 CST m=+0.312003901
3 , 执行:2019-12-14 15:45:16.2396051 +0800 CST m=+0.712089501
4 , 执行:2019-12-14 15:45:16.6395169 +0800 CST m=+1.112001301
5 , 执行:2019-12-14 15:45:17.0385893 +0800 CST m=+1.511073701
context canceled
7 , 执行:2019-12-14 15:45:17.440514 +0800 CST m=+1.912998401
8 , 执行:2019-12-14 15:45:17.8405152 +0800 CST m=+2.312999601
9 , 执行:2019-12-14 15:45:18.2405402 +0800 CST m=+2.713024601
10 , 执行:2019-12-14 15:45:18.6405179 +0800 CST m=+3.113002301

实用于容许阻塞期待的场景,比方生产音讯队列的音讯,能够限定最大的生产速率,过大了就会被限流防止消费者负载过高。

第三类办法:

func (lim *Limiter) Reserve() *Reservation
func (lim *Limiter) ReserveN(now time.Time, n int) *Reservation

与之前的两类办法不同的是 Reserve/ReserveN 返回了 Reservation 实例。Reservation 在 API 文档中有 5 个办法:

func (r *Reservation) Cancel() // 相当于 CancelAt(time.Now())
func (r *Reservation) CancelAt(now time.Time)
func (r *Reservation) Delay() time.Duration // 相当于 DelayFrom(time.Now())
func (r *Reservation) DelayFrom(now time.Time) time.Duration
func (r *Reservation) OK() bool

通过这 5 个办法能够让开发者依据业务场景进行操作,相比前两类的自动化,这样的操作显得简单多了。通过一个小示例来学习 Reserve/ReserveN:

func ReserveNDemo() {limter := rate.NewLimiter(10, 5)
   i := 0
   for {
      i++
      reserve := limter.ReserveN(time.Now(), 4)
      // 如果为 flase 阐明拿不到指定数量的令牌,比方须要的令牌数大于令牌桶容量的场景
      if !reserve.OK() {return}
      ts := reserve.Delay()
      time.Sleep(ts)
      fmt.Println("执行:", time.Now(),ts)
      if i == 10 {return}
   }
}

执行后果:

执行:2019-12-14 16:22:26.6446468 +0800 CST m=+0.008000201 0s
执行:2019-12-14 16:22:26.9466454 +0800 CST m=+0.309998801 247.999299ms
执行:2019-12-14 16:22:27.3446473 +0800 CST m=+0.708000701 398.001399ms
执行:2019-12-14 16:22:27.7456488 +0800 CST m=+1.109002201 399.999499ms
执行:2019-12-14 16:22:28.1456465 +0800 CST m=+1.508999901 398.997999ms
执行:2019-12-14 16:22:28.5456457 +0800 CST m=+1.908999101 399.0003ms
执行:2019-12-14 16:22:28.9446482 +0800 CST m=+2.308001601 399.001099ms
执行:2019-12-14 16:22:29.3446524 +0800 CST m=+2.708005801 399.998599ms
执行:2019-12-14 16:22:29.7446514 +0800 CST m=+3.108004801 399.9944ms
执行:2019-12-14 16:22:30.1446475 +0800 CST m=+3.508000901 399.9954ms

如果在执行 Delay() 之前操作 Cancel() 那么返回的工夫距离就会为 0,意味着能够立刻执行操作,不进行限流。

func ReserveNDemo2() {limter := rate.NewLimiter(5, 5)
   i := 0
   for {
      i++
      reserve := limter.ReserveN(time.Now(), 4)
      // 如果为 flase 阐明拿不到指定数量的令牌,比方须要的令牌数大于令牌桶容量的场景
      if !reserve.OK() {return}

      if i == 6 || i == 5 {reserve.Cancel()
      }
      ts := reserve.Delay()
      time.Sleep(ts)
      fmt.Println(i, "执行:", time.Now(), ts)
      if i == 10 {return}
   }
}

执行后果:

1 执行:2019-12-14 16:25:45.7974857 +0800 CST m=+0.007005901 0s
2 执行:2019-12-14 16:25:46.3985135 +0800 CST m=+0.608033701 552.0048ms
3 执行:2019-12-14 16:25:47.1984796 +0800 CST m=+1.407999801 798.9722ms
4 执行:2019-12-14 16:25:47.9975269 +0800 CST m=+2.207047101 799.0061ms
5 执行:2019-12-14 16:25:48.7994803 +0800 CST m=+3.009000501 799.9588ms
6 执行:2019-12-14 16:25:48.7994803 +0800 CST m=+3.009000501 0s
7 执行:2019-12-14 16:25:48.7994803 +0800 CST m=+3.009000501 0s
8 执行:2019-12-14 16:25:49.5984782 +0800 CST m=+3.807998401 798.0054ms
9 执行:2019-12-14 16:25:50.3984779 +0800 CST m=+4.607998101 799.0075ms
10 执行:2019-12-14 16:25:51.1995131 +0800 CST m=+5.409033301 799.0078ms

看到这里 time/rate 的限流形式曾经实现,除了上述的三类限流形式,time/rate 还提供了动静调整限流器参数的性能。相干 API 如下:

func (lim *Limiter) SetBurst(newBurst int) // 相当于 SetBurstAt(time.Now(), newBurst).
func (lim *Limiter) SetBurstAt(now time.Time, newBurst int)// 重设令牌桶的容量
func (lim *Limiter) SetLimit(newLimit Limit) // 相当于 SetLimitAt(time.Now(), newLimit)
func (lim *Limiter) SetLimitAt(now time.Time, newLimit Limit)// 重设放入令牌的速率

这四个办法能够让程序依据本身的状态动静的调整令牌桶速率和令牌桶容量。

结尾

通过上述一系列解说,置信大家对各个限流的利用场景和优缺点也有了大抵的把握,心愿在日常开发中有所帮忙。限流仅仅是整个服务治理中的一个小环节,须要与多种技术联合应用,才能够更好的晋升服务的稳定性的同时进步用户体验。

附录

https://github.com/uber-go/ra…
https://en.wikipedia.org/wiki…
https://www.cs.columbia.edu/~…
https://github.com/alibaba/Se…
https://tools.ietf.org/html/r…
https://www.yiichina.com/doc/…
https://github.com/RussellLuo…
http://zim.logdown.com/posts/…
https://www.yuque.com/clip/de…

正文完
 0