速率限制;它限制了某種資源在某段時間內 被訪問的次數。 資源可以是任何東西: API連接, 磁盤讀寫, 網絡包, 異常。 通常情況下 用戶對系統的訪問應當 被沙盒化,既不會影響其他用戶的活動, 也不會受到其他用戶的影響。 訪問 收費系統 時, 速率限制可以使你與客戶保持良好的關係。Google 的雲服務中 就用到了 速率限制。 Go語言 中 如何 進行限速呢? 大多數的限速 是基於 令牌桶算法的。 要想訪問資源,必須擁有資源的訪問令牌,沒有令牌的請求會被拒絕。 桶的深度爲 d ;代表可以存放 d個令牌。
先看一個沒有使用限速 的例子:
type APIConnection struct{}
func Open() *APIConnection{
return &APIConnection{}
}
func (a *APIConnection) ReadFile(ctx context.Context) error{
//執行一些操作
return nil
}
func (a *APIConnection) ResolveAddress(ctx context.Context) error{
//執行一些操作
return nil
}
func main() {
defer log.Printf("Done.")
log.SetOutput(os.Stdout)
log.SetFlags(log.Ltime | log.LUTC)
apiConnection := Open()
var wg sync.WaitGroup
wg.Add(20)
for i:=0 ; i<10; i++{
go func() {
defer wg.Done()
err := apiConnection.ReadFile(context.Background())
if err != nil{
log.Printf("cannot ReadFile: %v", err)
}
log.Printf("ReadFile")
}()
}
for i:=0; i<10; i++{
go func() {
defer wg.Done()
err := apiConnection.ResolveAddress(context.Background())
if err != nil{
log.Printf("cannot ResolveAddress: %v", err)
}
log.Printf("ResolveAddress")
}()
}
wg.Wait()
}
打算把 限速 放在 APIConnection 中,但通常限速器會在服務器上運行,這可以防止用戶輕易的繞過它。 使用 golang.org/x/time/rate 中 令牌桶限速器實現。 創建一個Limiter func NewLimiter(r Limit, b int) *Limiter 使用Limiter 的 Wait 來 阻塞我們的請求,直到獲得訪問令牌。
func Open() *APIConnection{
return &APIConnection{
rateLimiter: rate.NewLimiter(rate.Limit(1), 1),
}
}
type APIConnection struct{
rateLimiter *rate.Limiter
}
func (a *APIConnection) ReadFile(ctx context.Context) error{
if err := a.rateLimiter.Wait(ctx); err != nil{
return err
}
//在這裏執行一些邏輯
return nil
}
func (a *APIConnection) ResolveAddress(ctx context.Context) error{
if err := a.rateLimiter.Wait(ctx); err != nil{
return err
}
//在這裏執行一些邏輯
return nil
}
可能 會想要建立多層次的限制:用細粒度的控制 來限制每秒的請求,用粗粒度的控制來限制每分鐘,每小時 或每天的請求。 在某些情況下,可以用單一的 限速器 來做到這一點。然而並不能適應所有情況, 所以呢,把不同粒度的限速器獨立,然後將它們組合成一個限速器組 來管理 會更容易達到目的。
type RateLimiter interface {
Wait(ctx context.Context) error
Limit() rate.Limit
}
type multiLimiter struct{
limiters []RateLimiter
}
func MultiLimiter(limiters ...RateLimiter) *multiLimiter{
byLimit := func(i, j int) bool {
return limiters[i].Limit() < limiters[j].Limit()
}
sort.Slice(limiters, byLimit)
return &multiLimiter{limiters:limiters}
}
func (l *multiLimiter) Wait(ctx context.Context) error{
for _, l := range l.limiters {
if err := l.Wait(ctx); err != nil{
return err
}
}
return nil
}
//速度上 以 最慢的 那個 返回了,針對下面的 例子 就是 每分鐘十個 返回了
func (l *multiLimiter) Limit() rate.Limit{
return l.limiters[0].Limit()
}
func Per(eventCount int, duration time.Duration) rate.Limit{
return rate.Every(duration/time.Duration(eventCount))
}
type APIConnection struct{
rateLimiter RateLimiter
}
func (a *APIConnection) ReadFile(ctx context.Context) error{
if err := a.rateLimiter.Wait(ctx); err != nil{
return err
}
//執行一些邏輯
return nil
}
func (a *APIConnection) ResolveAddress(ctx context.Context) error{
if err := a.rateLimiter.Wait(ctx); err != nil{
return err
}
//執行一些邏輯
return nil
}
func Open() *APIConnection {
//這裏定義 每秒 的限制, 避免突發請求
secondLimit := rate.NewLimiter(Per(2, time.Second),1)
//這裏 定義每分鐘的 限制, 爲用戶提供初始池。 每秒的限制 將確保我們的系統 不會因爲突發的請求 而超載
minuteLimit := rate.NewLimiter(Per(10, time.Minute), 10)
return &APIConnection{
rateLimiter: MultiLimiter(secondLimit, minuteLimit),
}
}
//10:44:19 ReadFile
//10:44:19 ReadFile
//10:44:20 ReadFile
//10:44:20 ResolveAddress
//10:44:21 ReadFile
//10:44:21 ResolveAddress
//10:44:22 ResolveAddress
//10:44:22 ResolveAddress
//10:44:23 ResolveAddress
//10:44:23 ResolveAddress
//10:44:25 ResolveAddress
//10:44:31 ResolveAddress
//10:44:37 ResolveAddress
//10:44:43 ReadFile
//10:44:49 ReadFile
//10:44:55 ReadFile
//10:45:01 ReadFile
//10:45:07 ReadFile
//10:45:13 ReadFile
//10:45:19 ResolveAddress
//10:45:19 Done.
分析: 客戶端 每秒 發出兩個請求。 直到第十一個請求,我們開始每6秒種 發出一次請求。 這是因爲 耗盡了 每分鐘限速器的可用令牌,所以限制了請求速度。 除了考慮時間維度, 還有可能 使用其他維度來限制。 如:除了對API請求的數量有一些限制, 同時也可能對其他資源(如磁盤訪問,網絡訪問等)有限制。
type APIConnection struct{
networkLimit,
diskLimit,
apiLimit RateLimiter
}
func Open() *APIConnection{
return &APIConnection{
apiLimit: MultiLimiter(
rate.NewLimiter(Per(2, time.Second), 2),
rate.NewLimiter(Per(10, time.Minute), 10),
),
diskLimit:MultiLimiter(
rate.NewLimiter(rate.Limit(1), 1),
),
networkLimit:MultiLimiter(
rate.NewLimiter(Per(3, time.Second), 3),
),
}
}
func (a *APIConnection) ReadFile(ctx context.Context) error{
err := MultiLimiter(a.apiLimit, a.diskLimit).Wait(ctx)
if err != nil {
return err
}
//這裏執行一些邏輯
return nil
}
func (a *APIConnection) ResolveAddress(ctx context.Context) error{
err := MultiLimiter(a.apiLimit, a.networkLimit).Wait(ctx)
if err != nil{
return err
}
//這裏執行一些邏輯
return nil
}
//12:18:33 ReadFile
//12:18:33 ResolveAddress
//12:18:33 ResolveAddress
//12:18:34 ReadFile
//12:18:35 ReadFile
//12:18:35 ResolveAddress
//12:18:35 ResolveAddress
//12:18:36 ResolveAddress
//12:18:36 ReadFile
//12:18:37 ReadFile
//12:18:39 ReadFile
//12:18:45 ReadFile
//12:18:51 ResolveAddress
//12:18:57 ReadFile
//12:19:03 ResolveAddress
//12:19:09 ResolveAddress
//12:19:15 ResolveAddress
//12:19:21 ReadFile
//12:19:27 ResolveAddress
//12:19:33 ReadFile
//12:19:33 Done.
/**
rate.Limiter 還有一些特殊 技巧,遇到時在進一步研究
rate.Limiter 還有一些特殊 技巧,遇到時在進一步研究