首頁 > 軟體

Golang官方限流器庫實現限流範例詳解

2022-08-18 14:01:45

前言

在翻Golang官方庫的過程中,發現一個有趣的庫golang.org/x/time ,裡面只有一個類rate,研究了一下發現它是一個限流器,實現了很多的功能,當然它的核心原理並不複雜,也就是令牌桶演演算法。

令牌桶演演算法的原理是:令牌桶會不斷地把令牌新增到桶裡,而請求會從桶中獲取令牌,只有擁有令牌地請求才能被接受。因為桶中可以提前保留一些令牌,所以它允許一定地突發流量通過。

例子

下面是限流演演算法常見的寫法,首先判斷是否有令牌,如果有就通過,否則直接失敗。

package main
import (
	"fmt"
	"time"
	"golang.org/x/time/rate"
)
func main() {
        // 每0.1秒生成一個令牌,也就是一秒10個令牌,最大保留令牌上限10
	l := rate.NewLimiter(rate.Every(time.Second/10), 10)
	for i := 0; i < 10; i++ {
		go func(i int) {
			for {
                                // 判斷是否有令牌,如果有就輸出
				if l.Allow() {
					fmt.Printf("allow %dn", i)
				}
                                // 每0.5秒請求一次
				time.Sleep(time.Second / 2)
			}
		}(i)
	}
	time.Sleep(time.Second * 10)
}

上面的rate.Every(time.Second/10)會返回一個Limit型別,代表每秒生成多少個令牌。

這個庫還提供了另外一種寫法,等待直到有令牌為止(或超時):

func main() {
	l := rate.NewLimiter(rate.Every(time.Second/10), 100)
	for i := 0; i < 10; i++ {
		go func(i int) {
			for {
                                // 等待直到有令牌
				if err := l.Wait(context.TODO()); err != nil {
				} else {
					fmt.Printf("allow %dn", i)
				}
				time.Sleep(time.Second / 2)
			}
		}(i)
	}
	time.Sleep(time.Second * 10)
}

這樣在某些場景下我們可以讓請求等待一會,而不是直接失敗。

還有一個更加特殊的請求令牌方式,也就是先預留令牌,到指定時間不再需要去獲取令牌,直接執行操作即可:

func main() {
	l := rate.NewLimiter(rate.Every(time.Second/10), 10)
	for i := 0; i < 10; i++ {
		go func(i int) {
			for {
                                // 先預留令牌
				if r := l.Reserve(); r.OK() {
                                        // 休眠直到令牌生效
					time.Sleep(r.Delay())
					fmt.Printf("allow %dn", i)
				}
				time.Sleep(time.Second / 2)
			}
		}(i)
	}
	time.Sleep(time.Second * 10)
}

當然,如果預留的令牌不想使用了,也可以使用r.Cancel()歸還已預留的令牌。

上面的Allow()、Wait()、Reserve()都是一次消耗一個令牌,其實都有對應的AllowN()、WaitN()、ReserveN()方法,一次消耗N個令牌,這樣就可以根據任務消耗的資源靈活的消耗令牌。

實現

不管我們從Allow()、Wait()還是Reserve()進去,最終都會進入到reserveN(now time.Time, n int, maxFutureReserve time.Duration) Reservation 方法:

首先在進入方法的時候,會先處理兩種特殊情況:

        // 如果無限生成令牌,則直接返回
        if lim.limit == Inf {
		return Reservation{
			ok:        true,
			lim:       lim,
			tokens:    n,
			timeToAct: now,
		}
        // 如果不會生成令牌,則在初始令牌裡面拿,直到拿完為止
	} else if lim.limit == 0 {
		var ok bool
		if lim.burst >= n {
			ok = true
			lim.burst -= n
		}
		return Reservation{
			ok:        ok,
			lim:       lim,
			tokens:    lim.burst,
			timeToAct: now,
		}
	}

然後重新計算當前有多少令牌,減去要消耗的令牌:

        // 計算當前有多少令牌
	now, last, tokens := lim.advance(now)
	// 減去要消耗的N個令牌
	tokens -= float64(n)
        // 如果剩餘令牌為負數,那麼計算一下要等待多久才能拿到令牌
        var waitDuration time.Duration
	if tokens < 0 {
		waitDuration = lim.limit.durationFromTokens(-tokens)
	}
        // 判斷請求是否成功,maxFutureReserve代表最大可以等待的時間,也就是請求能否接收拿到令牌需要等待的時間
        ok := n <= lim.burst && waitDuration <= maxFutureReserve

餘下的程式碼就是更新限流器的狀態。

小結

可以看到這個令牌桶限流器實現的功能非常的豐富,如果需要令牌桶限流器,可以優先考慮使用這個實現。

以上就是Golang官方限流器庫使用範例詳解的詳細內容,更多關於Golang官方限流器庫的資料請關注it145.com其它相關文章!


IT145.com E-mail:sddin#qq.com