電流リミッタは、サービスの安定性を向上させるための非常に重要なコンポーネントであり、リクエスト レートを制限し、サービスを保護してサービスの過負荷を回避するために使用できます。電流制限器を実装するには多くの方法があります。一般的な電流制限アルゴリズムには、固定ウィンドウ、スライディング ウィンドウ、リーキー バケット、およびトークン バケットが含まれます。これについては、以前の記事 「一般的に使用される電流制限の応用」で説明しました。 「アルゴリズム、シナリオと実装原則」 では、これらの電流制限方式の特徴と適用シナリオについて説明しています。その中で、トークン バケットは、電流を制限しながら特定のバースト トラフィックにも対処できます。インターネット アプリケーションは、次の理由によりホット スポットになりやすいです。イベント時の突然のトラフィックピークの特性に適しています。
簡単に言うと、トークン バケットは固定サイズのバケットです。システムは一定の割合でトークンをバケットに入れます。バケットがいっぱいの場合は、トークンを入れません。一時的に置かれる。リクエストが比較的少ない場合、バケットは突然のトラフィックに対処するために最初にいくつかのトークンを「保存」でき、バケット内にトークンが残っている場合は継続的に引き出すことができます。トークンが残っていない場合は、トークンがバケットに配置されるまで待つ必要があります。
トークン バケットの電流制限の詳細については、次の記事を参照してください: 一般的に使用される電流制限アルゴリズムのアプリケーション シナリオと実装原理
いくつか学生たちはこれを読んでいます トークンバケットの原理を理解した後、実際に電流リミッターを実装して自分のプロジェクトに適用したいと思っています... なんというか、ホイールの構築は確かにレベルアップに役立ちますが、これは商用プロジェクトに適用されます。実際、ホイールを自分で構築する必要はありません。Golang が公式にホイールを構築してくれました...~!
Golang によって公式に提供される拡張ライブラリには、電流制限アルゴリズムの実装 (golang.org/x/time/rate
) が付属しています。この電流リミッターもトークン バケットに基づいて実装されます。
time/rate
Limiter
タイプのペアパッケージ 電流リミッターが定義されています。すべての電流制限機能は、Limiter
タイプに基づいて実装されます。その内部構造は次のとおりです:
type Limiter struct { mu sync.Mutex limit Limit burst int // 令牌桶的大小 tokens float64 last time.Time // 上次更新tokens的时间 lastEvent time.Time // 上次发生限速器事件的时间(通过或者限制都是限速器事件) }
その主要フィールドの機能は次のとおりです:
limit
字段表示往桶里放Token的速率,它的类型是Limit,是int64的类型别名。设置limit
时既可以用数字指定每秒向桶中放多少个Token,也可以指定向桶中放Token的时间间隔,其实指定了每秒放Token的个数后就能计算出放每个Token的时间间隔了。可以看到在 timer/rate
的限流器实现中,并没有单独维护一个 Timer 和队列去真的每隔一段时间向桶中放令牌,而是仅仅通过计数的方式表示桶中剩余的令牌。每次消费取 Token 之前会先根据上次更新令牌数的时间差更新桶中Token数。
大概了解了time/rate
限流器的内部实现后,下面的内容我们会集中介绍下该组件的具体使用方法:
我们可以使用以下方法构造一个限流器对象:
limiter := rate.NewLimiter(10, 100);
这里有两个参数:
r Limit
,设置的是限流器Limiter的limit
字段,代表每秒可以向 Token 桶中产生多少 token。Limit 实际上是 float64 的别名。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失败。
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) }
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") }
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 支持创建后动态调整速率和桶大小:
有了这两个方法,可以根据现有环境和条件以及我们的需求,动态地改变 Token 桶大小和速率。
今天我们总结了 Golang 官方限流器的使用方法,它是一种令牌桶算实现的限流器。其中 Wait/WaitN,Allow/AllowN 这两组方法在平时用的比较多,前者是消费Token时如果桶中Token不足可以让程序等待桶中新Token的放入(最好设置上等待时长)后者则是在桶中的Token不足时选择直接丢弃请求。
Golang が公式に提供する電流リミッターの実装に加えて、Uber のオープンソースの電流リミッター uber-go/ratelimit
も良い選択です Golang の公式の電流リミッターとの違いは、Uber のリミッターの流れですコントローラはリーキーバケットアルゴリズムによって実装されていますが、従来のリーキーバケットアルゴリズムが改良されており、興味のある学生は自ら体験することができます。
以上がGolang公式電流リミッターの使い方を詳しく解説の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。