iT邦幫忙

2023 iThome 鐵人賽

DAY 27
0

如果你曾經使用過API來獲取服務,那麽你可能經受過與速率限制相抗衡。速率限制使得某種資源每次訪 問的次數受限。資源可以是任何東西:API連接,磁盤I/O,網絡包,錯誤。
你有沒有想過為什麽會需要制定速率限制?為什麽不允許無限制地訪問系統?最明顯的答案是,通過對系
統進行速率限制,可以防止整個系統遭受攻擊。如果惡意用戶可以在他們的資源允許的情況下極快地訪問
你的系統,那麽他們可以做各種事情。
例如,他們可以用日志消息或有效的請求填滿服務器的磁盤。如果你的日志配置錯誤,他們甚至可能會執 行惡意的操作,然後提交足夠的請求,將任何活動記錄從日志中移出並放入/dev/null中導致日志系統完全 崩潰。他們可能試圖暴力訪問資源,或者執行分布式拒絕服務攻擊。如果你沒有對系統進行請求限制,你 的系統就成了一個走在街上不穿衣服的大美女。
更糟糕的是,非惡意請求有時也會造成上述結果。惡意使用並不是唯一的原因。
在分布式系統中,如果合法用戶正在以足夠高的速度執行操作,或者他們正在運行的代碼有問題,則合法用戶可能會降低系統的性能。這甚至可能導致我們之前討論的死亡螺旋。
從產品的角度來看,這太糟糕了!通常情況下,你希望向用戶提供某種類型的性能保證,以確保他們可以在一致的基礎上達到不錯的性能。如果一個用戶可以影響 該協議,那麽你的日子就不好過了。用戶對系統的訪問通常被沙盒化,既不會影響其他用戶的活動也不會 受其他用戶影響。如果你打破了這種思維模式,你的系統會表現出糟糕的設計使用感,甚至會導致用戶生氣或離開。

速率限制使得你可以通過某個界限來推斷系統的性能和穩定性。如果你需要擴展這些邊界,可以在大量測
試後以可控的方式進行。

我們先來看第一個程式,沒有速度限制。

package main

import (
	"context"
	"log"
	"os"
	"sync"
)

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()
}
func Open() *APIConnection {
	return &APIConnection{}
}

type APIConnection struct{}

func (a *APIConnection) ReadFile(ctx context.Context) error {
	// Pretend we do work here
	return nil
}

func (a *APIConnection) ResolveAddress(ctx context.Context) error {
	// Pretend we do work here
	return nil
}
輸出:
11:12:12 ReadFile
11:12:12 ResolveAddress
11:12:12 ResolveAddress
11:12:12 ReadFile
11:12:12 ReadFile
11:12:12 ResolveAddress
11:12:12 ResolveAddress
11:12:12 ResolveAddress
11:12:12 ReadFile
11:12:12 ResolveAddress
11:12:12 ReadFile
11:12:12 ReadFile
11:12:12 ReadFile
11:12:12 ResolveAddress
11:12:12 ReadFile
11:12:12 ReadFile
11:12:12 ResolveAddress
11:12:12 ResolveAddress
11:12:12 ReadFile
11:12:12 ResolveAddress
11:12:12 Done.

可以看到使用併發的情況下幾乎是同時完成,但可能會遇到以下問題:

1.資源壓力:沒有限制速度可能會對後端伺服器或系統造成壓力。如果後端伺服器或資源不是無限的,大量並發的請求可能會造成系統過載或耗盡資源。

2.伺服器過載:如果後端API、數據庫或其他系統不能夠同時處理大量的請求,它可能會變得反應遲鈍或完全崩潰。

3.網路壓力:大量的並發請求可能會導致網路飽和,進而影響其他服務或應用程式的效能。

4.資料競爭:在高度並發的環境下,如果不當地處理,可能會產生資料競爭的問題。這可以導致數據不一致或其他未預期的行為。

5.過度消耗成本:如果你的系統基於雲端並按使用量付費,則大量的並發請求可能會導致成本急劇上升。

6.限制或封鎖:如果你正在與外部API或服務進行互動,不加速度限制的大量請求可能會導致你的IP被暫時或永久封鎖。

7.不良的使用者體驗:當系統或伺服器因過多請求而變得不穩定時,最終的使用者可能會受到不良的影響,例如延遲回應、時常斷線或完全無法連接。


接著來看有速度限制的版本:

package main

import (
	"context"
	"golang.org/x/time/rate"
	"log"
	"os"
	"sync"
)

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()
}
func Open() *APIConnection {
	return &APIConnection{
		rateLimiter: rate.NewLimiter(rate.Limit(1), 1), // <1>
	}
}

type APIConnection struct {
	rateLimiter *rate.Limiter
}

func (a *APIConnection) ReadFile(ctx context.Context) error {
	if err := a.rateLimiter.Wait(ctx); err != nil { // <2>
		return err
	}
	// Pretend we do work here
	return nil
}

func (a *APIConnection) ResolveAddress(ctx context.Context) error {
	if err := a.rateLimiter.Wait(ctx); err != nil { // <2>
		return err
	}
	// Pretend we do work here
	return nil
}

1.第二個版本使用了速度限制器 (rate.Limiter),限制請求的速度。
2.在第二個版本中,每次呼叫 ReadFile 或 ResolveAddress 方法前,都會等待速度限制器的許可。
3.速度限制器的設定為每秒允許1個請求,這意味著如果有大量並發請求,則請求會被延遲,以保持速度限制。


上一篇
26.Replicated request
下一篇
28.Rate limiting-2
系列文
Concurrency in go 讀書心得30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言