用GCRA(通用蜂窝速率算法)对API请求进行速率限制的讨论
本文是富士通云技术2019年“降临节日历”的第21天文章。
第20天是由@foobaron先生开始提供“连接私人局域网”的私人桥接功能!
首先
一开始
开头
在开始时
起初
你好!我是在NIFCLOUD开发IaaS API的 @kzmake。
请问大家是如何处理API请求的瞬间爆发的情况呢?
为了最小化由请求突发引起的服务影响,在公开WebAPI时可能需要一些机制,如速率限制等。
今年因为这样那样的事情,
-
- WebAPIのレート制御のアルゴリズムとして、GCRAの解説
GCRA + Redisを利用したWebAPIのレート制限
我想用简略的方式来写这个故事!
?简而言之
GCRAはシンプルで高速なレート制限を実現する
GCRA + Redisで好きなパラメータをKeyとしてレート制限できる
GCRA + Redisで複数のサービス間でレート制限を共有できる
?GCRA(通用信道速率算法)是什么?
这是一种表示Leaky buket算法的方法,无需滴漏过程。它通过称为“单元”的固定大小的小包的单位来实现速率限制和调度。
Leaky bucket是指…… (Please provide the complete sentence or context for a more accurate paraphrase.)
具有一定数量的“漏斗”(受限的吞吐量)的桶。无法容纳超过其容量的桶。通过滴水的过程,可以表达平滑地恢复桶容量。
这种滴漏过程和存储容量也可以应用于API请求的速率限制等方面。

GCRA 是什么意思?
这是一个算法,根据从数据包排出被理论上插入的单元的时刻到达时间(TAT),来模拟Leaky bucket滴漏过程。
GCRA所使用的参数和公式。
-
- $t_a$
セルをパケットに挿入しようとした時刻
$TAT$
挿入されるセルがバケットから排出されるまでの時刻
$T$
パケットが1つのセルを排出する時間
$\tau$
バケットの時間的な最大容量
$バケットのセル最大容量 \times T$
$\tau + T$
パケットの排出感覚を含めて許容できる最大時間
当桶中的单元数为$n$时,理论到达时间$TAT$可以表示为:
TAT_{n+1} = \begin{cases}
t_a + T & (n=0) \\
TAT_{n} + T & (otherwise)
\end{cases}
這被表示為。
当尝试插入单元格时,应如何进行评估?
真正的地根据意思用中文翻译这段话:当尝试插入一个单元格时,若桶中已有$n$个单元格,则判断能否将单元格插入桶中。
TAT_{n+1} - (\tau + T) \geq t_a
我们会进行评估。将其整理一下,
TAT_{n} - \tau \geq t_a
邻居就住在旁边。
在GCRA中,使用到TAT来简单地表示了Leaky bucket中的滴水过程。
作为速度控制的功能
我们将考虑使用Redis以使其能够在多个服务中共享利用。
在GCRA中需要使用的Redis命令
GCRA可以通过软件方式将TAT永久化,并在TAT的时刻到期来实现(其中,ta是速率限制时确定的,τ和T是由速率限制系统决定的)。
如果想要从多个服务统一实施速率限制,可以使用Redis等工具,在不同服务之间共享这个$TAT$。
这次算法中使用的Redis命令是,
-
- SET
- EXPIRE
这只有两个选项,非常简单对吧。
(顺便提一下,虽然成本相同,但使用SETEX命令可以一次性设置多个配置。)
尽管这个算法很简单,我个人实现了它。
-
- github.com/brandur/redis-cell
- github.com/throttled/throttled
我会介绍两个利率限制。
使用brandur/redis-cell库
在github.com/brandur/redis-cell上发布了一个利用SETEX的Redis模块。只需将github.com/brandur/redis-cell作为Redis的一个模块引入,即可使用这个功能。
redis-cell命令的添加方式
以下是在github.com/brandur/redis-cell#Usage中介绍的命令。
CL.THROTTLE <key> <max_burst> <count per period> <period> [<quantity>]
Redis-cell命令与GCRA相关
CL.THROTTLE user123 15 30 60 1
▲ ▲ ▲ ▲ ▲
| | | | └───── apply 1 token (default if omitted)
| | └──┴─────── 30 tokens / 60 seconds
| └───────────── 15 max_burst
└─────────────────── key "user123"
创建一个名为user123的桶,最大容量为15个单元格。
如果我们将”执行命令”这个动作解释为”向桶中插入一个单元格”,那么
-
- 挿入されるセル数
1
$\tau$
15 * T
$T$
60/30
$\tau + T$
(15 + 1) * T
$t_a$
コマンドを叩いた時刻
与此有关。
redis-cell的响应
如果考虑将其用作API请求的速率限制时,
127.0.0.1:6379> CL.THROTTLE user123 15 30 60
1) (integer) 0
2) (integer) 16
3) (integer) 15
4) (integer) -1
5) (integer) 2
-
- 0:未被阻止
-
- 1:已被阻止
阻止的上限值+ 1
常见的X-RateLimit-Limit值
剩余阻止值
常见的X-RateLimit-Remaining值
用户需要等待重新发送请求的秒数[秒]
常见的重试时间值
在被允许期间为-1
距离重置上限值的秒数[秒]
常见的X-RateLimit-Reset值
看起来好像可以这样。
接下来只需要在各自的程序中准备好Redis客户端,并执行相应的命令即可。
被限制/被限制的使用
github.com/throttled/throttled也作为Golang包发布了。它作为一个存储器,可以对请求进行限制和调度。
-
- goredisstore
-
- memstore
- redigostore
由于支持这一点,似乎可以灵活使用。
使用throttled的程序示例
github.com/brandur/redis-cellと同等の機能を持っているので一例で紹介するのみとします。APIリクエストをレート制限するのであれば、とっても簡単に導入できますね。
mux := http.NewServeMux()
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
fmt.Fprintf(w, "Hello World")
})
store, err := memstore.New(65536)
if err != nil {
log.Fatal(err)
}
rateLimiter, err := throttled.NewGCRARateLimiter(store, throttled.RateQuota{MaxRate: throttled.PerMin(30), MaxBurst: 15})
if err != nil {
log.Fatal(err)
}
// Custom: func(r *http.Request) string { ... } により好きなキーを設定可能
varyBy := &throttled.VaryBy{Path: true}
httpRateLimiter := throttled.HTTPRateLimiter{
RateLimiter: rateLimiter,
VaryBy: varyBy,
}
http.ListenAndServe(":3000", httpRateLimiter.RateLimit(mux))
?最后
GCRA是一种高速且简单的算法,提供了精细的速率限制。通过使用github.com/brandur/redis-cell或github.com/throttled/throttled等工具,可以轻松地将速率限制功能添加到WebAPI中。
汇率限制
-
- 有限のリソースへのアクセスを抑制する
-
- パスワード試行回数のセキュリティ強化
- レート制限の解除や上限数の緩和などのマネタイズ
如果您有这样的目的,请一定考虑使用GCRA,因为它是可用的要素。
据说明天 @hunter9x 会写点什么!敬请期待!
? 仅需要一种选择,原文的中文释义如下:
? 参考 -> 以……作为参考
-
- Rate Limiting, Cells, and GCRA
-
- Generic cell rate algorithm – Wikipedia
-
- brandur/redis-cell: A Redis module that provides rate limiting in Redis as a single command.
- throttled/throttled: Package throttled implements rate limiting access to resources such as HTTP endpoints.