Ausführliche Erläuterung der Verwendung des offiziellen Strombegrenzers von Go

Machen Sie es sich zur Gewohnheit, gemeinsam zu schreiben! Dies ist der sechste Tag meiner Teilnahme an der „Nuggets Daily New Plan · April Update Challenge“, klicken Sie hier, um die Details der Veranstaltung anzuzeigen .

Der Strombegrenzer stellt eine sehr wichtige Komponente zur Verbesserung der Stabilität des Dienstes dar. Er kann verwendet werden, um die Anforderungsrate zu begrenzen und den Dienst vor Überlastung des Dienstes zu schützen. Es gibt viele Möglichkeiten, den Strombegrenzer zu implementieren.Gebräuchliche Strombegrenzungsalgorithmen umfassen ein festes Fenster, ein gleitendesFenster, einen Leaky-Bucket und einen Token-Bucket.

Einfach ausgedrückt stellt sich der Token-Bucket einen Bucket mit fester Größe vor.Das System legt Tokens mit einer konstanten Rate in den Bucket, und der Bucketwird nicht vorübergehend freigegeben, wenn der Bucket voll ist. Wenn die Anzahl der Anfragen relativ gering ist, kann der Bucket zunächst einige Token „speichern", um plötzlichen Datenverkehr zu bewältigen. Wenn sich noch Token im Bucket befinden, kann er die ganze Zeit verwendet werden. Wenn kein Token mehr vorhanden ist, müssen Sie warten, bis der Token in den Eimer gelegt wird.

Nachdem einige Schüler das Prinzip des Token Bucket verstanden haben, möchten sie unbedingt einen Strombegrenzer implementieren und auf ihre eigenen Projekte anwenden, em ... Wie soll ich sagen, ein Rad zu bauen ist in der Tat förderlich für die Verbesserung des eigenen Niveaus, aber wenn die Anwendung Bei kommerziellen Projekten müssen Sie die Räder nicht selbst bauen, Golang hat die Räder bereits für uns gebaut...~!

Die offiziell von Golang bereitgestellte Erweiterungsbibliothek enthält die Implementierung des Strombegrenzungsalgorithmus, dh golang.org/x/time/rate. Der Strombegrenzer ist ebenfalls basierend auf Token Bucket implementiert.

Die interne Struktur des Strombegrenzers

time/rateDer Typ des Pakets Limiterdefiniert den Strombegrenzer, und alle Strombegrenzungsfunktionen werden basierend auf Limiterdem Typ implementiert, und seine interne Struktur ist wie folgt:

type Limiter struct {
 mu     sync.Mutex
 limit  Limit
 burst  int // 令牌桶的大小
 tokens float64
 last time.Time // 上次更新tokens的时间
 lastEvent time.Time // 上次发生限速器事件的时间(通过或者限制都是限速器事件)
}
复制代码

Seine Hauptfelder sind:

  • limit: limitDas Feld gibt die Rate an, mit der Token in den Bucket gelegt werden.Sein Typ ist Limit, was der Typ-Alias ​​von int64 ist. Wenn Sie festlegen limit, können Sie Zahlen verwenden, um anzugeben, wie viele Token pro Sekunde in den Bucket gelegt werden, und Sie können auch das Zeitintervall für die Freigabe von Token in den Bucket angeben . Tatsächlich können Sie nach Angabe der Anzahl an Token pro Sekunde berechnen die Zeit für die Freigabe jedes Tokens.

  • burst: Die Größe des Token-Buckets.

  • tokens: Tokens im Bucket.

  • last: 上次往桶中放 Token 的时间。

  • lastEvent:上次发生限速器事件的时间(通过或者限制都是限速器事件)

可以看到在 timer/rate 的限流器实现中,并没有单独维护一个 Timer 和队列去真的每隔一段时间向桶中放令牌,而是仅仅通过计数的方式表示桶中剩余的令牌。每次消费取 Token 之前会先根据上次更新令牌数的时间差更新桶中Token数

大概了解了time/rate限流器的内部实现后,下面的内容我们会集中介绍下该组件的具体使用方法:

构造限流器

我们可以使用以下方法构造一个限流器对象:

limiter := rate.NewLimiter(10100);
复制代码

这里有两个参数:

  1. 第一个参数是 r Limit,设置的是限流器Limiter的limit字段,代表每秒可以向 Token 桶中产生多少 token。Limit 实际上是 float64 的别名。

  2. 第二个参数是 b int,b 代表 Token 桶的容量大小,也就是设置的限流器 Limiter 的burst字段。

那么,对于以上例子来说,其构造出的限流器的令牌桶大小为 100, 以每秒 10 个 Token 的速率向桶中放置 Token。

除了给r Limit参数直接指定每秒产生的 Token 个数外,还可以用 Every 方法来指定向桶中放置 Token 的间隔,例如:

limit := rate.Every(100 * time.Millisecond);
limiter := rate.NewLimiter(limit, 100);
复制代码

以上就表示每 100ms 往桶中放一个 Token。本质上也是一秒钟往桶里放 10 个。

使用限流器

Limiter 提供了三类方法供程序消费 Token,可以每次消费一个 Token,也可以一次性消费多个 Token。每种方法代表了当 Token 不足时,各自不同的对应手段,可以阻塞等待桶中Token补充,也可以直接返回取Token失败。

Wait/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)

当使用 Wait 方法消费 Token 时,如果此时桶内 Token 数组不足 (小于 N),那么 Wait 方法将会阻塞一段时间,直至 Token 满足条件。如果充足则直接返回。

这里可以看到,Wait 方法有一个 context 参数。我们可以设置 context 的 Deadline 或者 Timeout,来决定此次 Wait 的最长时间。

// 一直等到获取到桶中的令牌
err := limiter.Wait(context.Background())
if err != nil {
 fmt.Println("Error: ", err)
}

// 设置一秒的等待超时时间
ctx, _ := context.WithTimeout(context.Background(), time.Second * 1)
err := limiter.Wait(ctx)
if err != nil {
 fmt.Println("Error: ", err)
}
复制代码

Allow/AllowN

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

Allow 实际上就是对 AllowN(time.Now(),1) 进行简化的函数。

AllowN 方法表示,截止到某一时刻,目前桶中数目是否至少为 n 个,满足则返回 true,同时从桶中消费 n 个 token。反之不消费桶中的Token,返回false。

对应线上的使用场景是,如果请求速率超过限制,就直接丢弃超频后的请求。

if limiter.AllowN(time.Now(), 2) {
    fmt.Println("event allowed")
} else {
    fmt.Println("event not allowed")
}
复制代码

Reserve/ReserveN

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

Reserve 相当于 ReserveN(time.Now(), 1)

ReserveN 的用法就相对来说复杂一些,当调用完成后,无论 Token 是否充足,都会返回一个 *Reservation 对象。你可以调用该对象的Delay()方法,该方法返回的参数类型为time.Duration,反映了需要等待的时间,必须等到等待时间之后,才能进行接下来的工作。如果不想等待,可以调用Cancel()方法,该方法会将 Token 归还。

举一个简单的例子,我们可以这么使用 Reserve 方法。

r := limiter.Reserve()
f !r.OK() {
    // Not allowed to act! Did you remember to set lim.burst to be > 0 ?
    return
}
time.Sleep(r.Delay())
Act() // 执行相关逻辑
复制代码

动态调整速率和桶大小

Limiter 支持创建后动态调整速率和桶大小:

  1. SetLimit(Limit) 改变放入 Token 的速率

  2. SetBurst(int) 改变 Token 桶大小

有了这两个方法,可以根据现有环境和条件以及我们的需求,动态地改变 Token 桶大小和速率。

总结

Heute fassen wir die Verwendung des offiziellen Strombegrenzers von Golang zusammen, der ein Strombegrenzer ist, der durch Token-Bucket-Berechnung implementiert wird. Darunter Wait/WaitN und Allow/AllowN werden normalerweise häufiger verwendet, ersteres soll das Programm auf einen neuen Token im Bucket warten lassen, wenn der Token im Bucket beim Token-Verbrauch nicht ausreicht (es ist am besten, das Warten einzustellen time) Letzteres Es ist zu wählen, die Anfrage direkt zu verwerfen, wenn das Token im Bucket nicht ausreicht.

Neben der Implementierung des offiziell von Golang bereitgestellten Strombegrenzers stellt uber-go/ratelimitauch der Open-Source-Strombegrenzer von Uber eine gute Wahl dar. Im Gegensatz zum offiziellen Golang-Strombegrenzer wird der Strombegrenzer von Uber durch den Leaky-Bucket-Algorithmus implementiert, ist jedoch nicht für herkömmliche Anwendungen geeignet Der Leaky-Bucket-Algorithmus wurde verbessert und interessierte Schüler können ihn selbst ausprobieren.

Ich denke du magst

Origin juejin.im/post/7083811372420562951
Empfohlen
Rangfolge