欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Golang限流器time/rate設(shè)計與實現(xiàn)詳解

 更新時間:2024年03月05日 11:34:07   作者:Meng小羽  
在?Golang?庫中官方給我們提供了限流器的實現(xiàn)golang.org/x/time/rate,它是基于令牌桶算法(Token?Bucket)設(shè)計實現(xiàn)的,下面我們就來看看他的具體使用吧

限流器是后臺服務(wù)中十分重要的組件,在實際的業(yè)務(wù)場景中使用居多,其設(shè)計在微服務(wù)、網(wǎng)關(guān)、和一些后臺服務(wù)中會經(jīng)常遇到。限流器的作用是用來限制其請求的速率,保護后臺響應(yīng)服務(wù),以免服務(wù)過載導(dǎo)致服務(wù)不可用現(xiàn)象出現(xiàn)。

限流器的實現(xiàn)方法有很多種,例如 Token Bucket、滑動窗口法、Leaky Bucket等。

在 Golang 庫中官方給我們提供了限流器的實現(xiàn)golang.org/x/time/rate,它是基于令牌桶算法(Token Bucket)設(shè)計實現(xiàn)的。

令牌桶算法

令牌桶設(shè)計比較簡單,可以簡單的理解成一個只能存放固定數(shù)量雪糕?的一個冰箱,每個請求可以理解成來拿雪糕的人,有且只能每一次請求拿一塊?,那雪糕拿完了會怎么樣呢?這里會有一個固定放雪糕的工人,并且他往冰箱里放雪糕的頻率都是一致的,例如他 1s 中只能往冰箱里放 10 塊雪糕,這里就可以看出請求響應(yīng)的頻率了。

令牌桶設(shè)計概念:

  • 令牌:每次請求只有拿到 Token 令牌后,才可以繼續(xù)訪問;
  • :具有固定數(shù)量的桶,每個桶中最多只能放設(shè)計好的固定數(shù)量的令牌;
  • 入桶頻率:按照固定的頻率往桶中放入令牌,放入令牌不能超過桶的容量。

也就是說,基于令牌桶設(shè)計算法就限制了請求的速率,達(dá)到請求響應(yīng)可控的目的,特別是針對于高并發(fā)場景中突發(fā)流量請求的現(xiàn)象,后臺就可以輕松應(yīng)對請求了,因為到后端具體服務(wù)的時候突發(fā)流量請求已經(jīng)經(jīng)過了限流了。

具體設(shè)計

限流器定義

type Limiter struct {
	mu        sync.Mutex // 互斥鎖(排他鎖)
	limit     Limit      // 放入桶的頻率  float64 類型
	burst     int        // 桶的大小
	tokens    float64    // 令牌 token 當(dāng)前剩余的數(shù)量
	last      time.Time  // 最近取走 token 的時間
	lastEvent time.Time  // 最近限流事件的時間
}

limit、burst 和 token 是這個限流器中核心的參數(shù),請求并發(fā)的大小在這里實現(xiàn)的。

在令牌發(fā)放之后,會存儲在 Reservation 預(yù)約對象中:

type Reservation struct {
	ok        bool      // 是否滿足條件分配了 token
	lim       *Limiter  // 發(fā)送令牌的限流器
	tokens    int       // 發(fā)送 token 令牌的數(shù)量
	timeToAct time.Time // 滿足令牌發(fā)放的時間
	limit     Limit     // 令牌發(fā)放速度
}

消費 Token

Limiter 提供了三類方法供用戶消費 Token,用戶可以每次消費一個 Token,也可以一次性消費多個 Token。而每種方法代表了當(dāng) Token 不足時,各自不同的對應(yīng)手段。

Wait、WaitN

func (lim *Limiter) Wait(ctx context.Context) (err error)
func (lim *Limiter) WaitN(ctx context.Context, n int) (err error)

其中,Wait 就是 WaitN(ctx, 1),在下面的方法介紹實現(xiàn)也是一樣的。

使用 Wait 方法消費 Token 時,如果此時桶內(nèi) Token 數(shù)組不足 ( 小于 n ),那么 Wait 方法將會阻塞一段時間,直至 Token 滿足條件。如果充足則直接返回。

Allow、AllowN

func (lim *Limiter) Allow() bool
func (lim *Limiter) AllowN(now time.Time, n int) bool 

AllowN 方法表示,截止到當(dāng)前某一時刻,目前桶中數(shù)目是否至少為 n 個,滿足則返回 true,同時從桶中消費 n 個 token。 反之返回不消費 Token,false。

通常對應(yīng)這樣的線上場景,如果請求速率過快,就直接丟到某些請求。

Reserve、ReserveN

官方提供的限流器有阻塞等待式的 Wait,也有直接判斷方式的 Allow,還有提供了自己維護預(yù)留式的,但核心的實現(xiàn)都是下面的 reserveN 方法。

func (lim *Limiter) Reserve() *Reservation
func (lim *Limiter) ReserveN(now time.Time, n int) *Reservation

當(dāng)調(diào)用完成后,無論 Token 是否充足,都會返回一個Reservation *對象。

你可以調(diào)用該對象的 Delay() 方法,該方法返回了需要等待的時間。如果等待時間為 0,則說明不用等待。 必須等到等待時間結(jié)束之后,才能進行接下來的工作。

或者,如果不想等待,可以調(diào)用 Cancel() 方法,該方法會將 Token 歸還。

func (lim *Limiter) reserveN(now time.Time, n int, maxFutureReserve time.Duration) Reservation {
	lim.mu.Lock()

	// 首先判斷是否放入頻率是否為無窮大
	// 如果為無窮大,說明暫時不限流
	if lim.limit == Inf {
		lim.mu.Unlock()
		return Reservation{
			ok:        true,
			lim:       lim,
			tokens:    n,
			timeToAct: now,
		}
	}

	// 拿到截至 now 時間時
	// 可以獲取的令牌 tokens 數(shù)量及上一次拿走令牌的時間 last
	now, last, tokens := lim.advance(now)

	// 更新 tokens 數(shù)量
	tokens -= float64(n)

	// 如果 tokens 為負(fù)數(shù),代表當(dāng)前沒有 token 放入桶中
	// 說明需要等待,計算等待的時間
	var waitDuration time.Duration
	if tokens < 0 {
		waitDuration = lim.limit.durationFromTokens(-tokens)
	}

	// 計算是否滿足分配條件
	// 1、需要分配的大小不超過桶的大小
	// 2、等待時間不超過設(shè)定的等待時長
	ok := n <= lim.burst && waitDuration <= maxFutureReserve

	// 預(yù)處理 reservation
	r := Reservation{
		ok:    ok,
		lim:   lim,
		limit: lim.limit,
	}
	// 若當(dāng)前滿足分配條件
	// 1、設(shè)置分配大小
	// 2、滿足令牌發(fā)放的時間 = 當(dāng)前時間 + 等待時長
	if ok {
		r.tokens = n
		r.timeToAct = now.Add(waitDuration)
	}

	// 更新 limiter 的值,并返回
	if ok {
		lim.last = now
		lim.tokens = tokens
		lim.lastEvent = r.timeToAct
	} else {
		lim.last = last
	}

	lim.mu.Unlock()
	return r
}

具體使用

rate 包中提供了對限流器的使用,只需要指定 limit(放入桶中的頻率)、burst(桶的大小)。

func NewLimiter(r Limit, b int) *Limiter {
	return &Limiter{
		limit: r, // 放入桶的頻率
		burst: b, // 桶的大小
	}
}

在這里,使用一個 http api 來簡單的驗證一下 time/rate 的強大:

func main() {
	r := rate.Every(1 * time.Millisecond)
	limit := rate.NewLimiter(r, 10)
	http.HandleFunc("/", func(writer http.ResponseWriter, request *http.Request) {
		if limit.Allow() {
			fmt.Printf("請求成功,當(dāng)前時間:%s\n", time.Now().Format("2006-01-02 15:04:05"))
		} else {
			fmt.Printf("請求成功,但是被限流了。。。\n")
		}
	})

	_ = http.ListenAndServe(":8081", nil)
}

在這里,我把桶設(shè)置成了每一毫秒投放一次令牌,桶容量大小為 10,起一個 http 的服務(wù),模擬后臺 API。

接下來做一個壓力測試,看看效果如何:

func GetApi() {
	api := "http://localhost:8081/"
	res, err := http.Get(api)
	if err != nil {
		panic(err)
	}
	defer res.Body.Close()

	if res.StatusCode == http.StatusOK {
		fmt.Printf("get api success\n")
	}
}

func Benchmark_Main(b *testing.B) {
	for i := 0; i < b.N; i++ {
		GetApi()
	}
}

效果如下:

......
請求成功,當(dāng)前時間:2020-08-24 14:26:52
請求成功,但是被限流了。。。
請求成功,但是被限流了。。。
請求成功,但是被限流了。。。
請求成功,但是被限流了。。。
請求成功,但是被限流了。。。
請求成功,當(dāng)前時間:2020-08-24 14:26:52
請求成功,但是被限流了。。。
請求成功,但是被限流了。。。
請求成功,但是被限流了。。。
請求成功,但是被限流了。。。
......

在這里,可以看到,當(dāng)使用 AllowN 方法中,只有當(dāng)令牌 Token 生產(chǎn)出來,才可以消費令牌,繼續(xù)請求,剩余的則是將其請求拋棄,當(dāng)然在實際的業(yè)務(wù)處理中,可以用比較友好的方式反饋給前端。

在這里,先有的幾次請求都會成功,是因為服務(wù)啟動后,令牌桶會初始化,將令牌放入到桶中,但是隨著突發(fā)流量的請求,令牌按照預(yù)定的速率生產(chǎn)令牌,就會出現(xiàn)明顯的令牌供不應(yīng)求的現(xiàn)象。

到此這篇關(guān)于Golang限流器time/rate設(shè)計與實現(xiàn)詳解的文章就介紹到這了,更多相關(guān)Go限流器time/rate內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 談?wù)刧olang的netpoll原理解析

    談?wù)刧olang的netpoll原理解析

    本文詳細(xì)介紹了Go語言中netpoll部分的實現(xiàn)細(xì)節(jié)和協(xié)程阻塞調(diào)度原理,特別是epoll在Linux環(huán)境下的工作原理,Go語言通過將epoll操作放在runtime包中,結(jié)合運行時調(diào)度功能,實現(xiàn)了高效的協(xié)程I/O操作,感興趣的朋友跟隨小編一起看看吧
    2024-11-11
  • Mac GoLand打不開(閃退)也不報錯的解決方案

    Mac GoLand打不開(閃退)也不報錯的解決方案

    這篇文章主要介紹了Mac GoLand打不開(閃退)也不報錯的解決方案,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2021-04-04
  • 使用docker構(gòu)建golang線上部署環(huán)境的步驟詳解

    使用docker構(gòu)建golang線上部署環(huán)境的步驟詳解

    這篇文章主要介紹了使用docker構(gòu)建golang線上部署環(huán)境的步驟,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧。
    2017-11-11
  • Golang的strings.Split()踩坑記錄

    Golang的strings.Split()踩坑記錄

    工作中,當(dāng)我們需要對字符串按照某個字符串切分成字符串?dāng)?shù)組數(shù)時,常用到strings.Split(),本文主要介紹了Golang的strings.Split()踩坑記錄,感興趣的可以了解一下
    2022-05-05
  • Go程序性能優(yōu)化及pprof使用方法詳解

    Go程序性能優(yōu)化及pprof使用方法詳解

    這篇文章主要為大家詳細(xì)介紹了Go程序性能優(yōu)化及pprof的使用方法,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2017-11-11
  • Go語言學(xué)習(xí)教程之結(jié)構(gòu)體的示例詳解

    Go語言學(xué)習(xí)教程之結(jié)構(gòu)體的示例詳解

    結(jié)構(gòu)體是一個序列,包含一些被命名的元素,這些被命名的元素稱為字段(field),每個字段有一個名字和一個類型。本文通過一些示例帶大家深入了解Go語言中結(jié)構(gòu)體的使用,需要的可以參考一下
    2022-09-09
  • Go程序的init函數(shù)在什么時候執(zhí)行

    Go程序的init函數(shù)在什么時候執(zhí)行

    在Go語言中,init?函數(shù)是一個特殊的函數(shù),它用于執(zhí)行程序的初始化任務(wù),本文主要介紹了Go程序的init函數(shù)在什么時候執(zhí)行,感興趣的可以了解一下
    2023-10-10
  • Go語言之使用pprof工具查找goroutine(協(xié)程)泄漏

    Go語言之使用pprof工具查找goroutine(協(xié)程)泄漏

    這篇文章主要介紹了Go語言之使用pprof工具查找goroutine(協(xié)程)泄漏,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2024-01-01
  • GoLang中Module的基本使用方法

    GoLang中Module的基本使用方法

    Go module是從Go 1.11版本才引入的新功能,下面這篇文章主要給大家介紹了關(guān)于GoLang中Module的基本使用方法,文中通過實例代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2023-01-01
  • Goland 生成可執(zhí)行文件的操作

    Goland 生成可執(zhí)行文件的操作

    這篇文章主要介紹了Goland 生成可執(zhí)行文件的操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2020-12-12

最新評論