Go併發模式/設計模式之速率限制(API速率限制)

速率限制;它限制了某種資源在某段時間內 被訪問的次數。 資源可以是任何東西: 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 還有一些特殊 技巧,遇到時在進一步研究

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章