Go語言Ratelimit服務流量限制

2020-07-16 10:05:04
計算機程式可依據其瓶頸分為磁碟 IO 瓶頸型,CPU 計算瓶頸型,?絡頻寬瓶頸型,分散式場景下有時候也會外部系統?導致?身瓶頸。

Web 系統打交道最多的是?絡,?論是接收,解析?戶請求,存取儲存,還是把響應資料返回給?戶,都是要??絡的。在沒有 epoll/kqueue 之類的系統提供的 IO 多路複?接?之前,多個核?的現代計算機最頭痛的是 C10k 問題,C10k 問題會導致計算機沒有辦法充分利? CPU 來處理更多的?戶連線,進?沒有辦法通過優化程式提升CPU利?率來處理更多的請求。

?從 Linux 實現了 epoll,FreeBSD 實現了 kqueue ,這個問題基本解決了,我們可以借助核心提供的 API 輕鬆解決當年的 C10k 問題,也就是說如今如果你的程式主要是和?絡打交道,那麼瓶頸?定在?戶程式?不在作業系統核心。

隨著時代的發展,程式設計語?對這些系統調??進?步進?了封裝,如今做應?層開發,?乎不會在程式中看到 epoll 之類的字眼,?多數時候我們就只要聚焦在業務邏輯上就好。

Go語言的 net 庫針對不同平台封裝了不同的 syscall API,http 庫?是構建在 net 庫之上,所以在 Go語?中我們可以藉助標準庫,很輕鬆地寫出?效能的 http 服務,下?是?個簡單的 hello world 服務的程式碼:
package main

import (
    "io"
    "log"
    "net/http"
)

func sayhello(wr http.ResponseWriter, r *http.Request) {
    wr.WriteHeader(200)
    io.WriteString(wr, "hello world")
}
func main() {
    http.HandleFunc("/", sayhello)
    err := http.ListenAndServe(":9090", nil)
    if err != nil {
        log.Fatal("ListenAndServe:", err)
    }
}
我們需要衡量?下這個 Web 服務的吞吐量,再具體?些,實際上就是接?的 QPS。借助 wrk,在家?電腦 Macbook Pro 上對這個 hello world 服務進?基準測試,Mac 的硬體情況如下:

CPU: Intel(R) Core(TM) i5-5257U CPU @ 2.70GHz
Core: 2
Threads: 4
Graphics/Displays:
    Chipset Model: Intel Iris Graphics 6100
        Resolution: 2560 x 1600 Retina
    Memory Slots:
        Size: 4 GB
        Speed: 1867 MHz
        Size: 4 GB
        Speed: 1867 MHz
    Storage:
        Size: 250.14 GB (250,140,319,744 bytes)
        Media Name: APPLE SSD SM0256G Media
        Size: 250.14 GB (250,140,319,744 bytes)
        Medium Type: SSD

測試結果:

~ ??? wrk -c 10 -d 10s -t10 http://localhost:9090
Running 10s test @ http://localhost:9090
    10 threads and 10 connections
    Thread Stats Avg Stdev Max +/- Stdev
        Latency 339.99us 1.28ms 44.43ms 98.29%
        Req/Sec 4.49k 656.81 7.47k 73.36%
    449588 requests in 10.10s, 54.88MB read
Requests/sec: 44513.22
Transfer/sec: 5.43MB

~ ??? wrk -c 10 -d 10s -t10 http://localhost:9090
Running 10s test @ http://localhost:9090
    10 threads and 10 connections
    Thread Stats Avg Stdev Max +/- Stdev
        Latency 334.76us 1.21ms 45.47ms 98.27%
        Req/Sec 4.42k 633.62 6.90k 71.16%
    443582 requests in 10.10s, 54.15MB read
Requests/sec: 43911.68
Transfer/sec: 5.36MB

~ ??? wrk -c 10 -d 10s -t10 http://localhost:9090
Running 10s test @ http://localhost:9090
    10 threads and 10 connections
    Thread Stats Avg Stdev Max +/- Stdev
        Latency 379.26us 1.34ms 44.28ms 97.62%
        Req/Sec 4.55k 591.64 8.20k 76.37%
    455710 requests in 10.10s, 55.63MB read
Requests/sec: 45118.57
Transfer/sec: 5.51MB

多次測試的結果在 4 萬左右的 QPS 浮動,響應時間最多也就是 40ms 左右,對於?個 Web 程式來說,這已經是很不錯的成績了,我們只是照抄了別?的範例程式碼,就完成了?個?效能的 hello world 伺服器,是不是很有成就感?

這還只是家? PC,線上伺服器?多都是 24 核?起,32G 記憶體 +,CPU 基本都是 Intel i7。所以同樣的程式在伺服器上運?會得到更好的結果。

這?的 hello world 服務沒有任何業務邏輯。真實環境的程式要複雜得多,有些程式偏?絡 IO 瓶頸,例如?些 CDN 服務、Proxy 服務;有些程式偏 CPU/GPU 瓶頸,例如登陸校驗服務、影象處理服務;有些程式瓶頸偏磁碟,例如專?的儲存系統,資料庫。

不同的程式瓶頸會體現在不同的地?,這?提到的這些功能單?的服務相對來說還算容易分析。如果碰到業務邏輯複雜程式碼量巨?的模組,其瓶頸並不是三下五除?可以推測出來的,還是需要從壓?測試中得到更為精確的結論。

對於 IO/Network 瓶頸類的程式,其表現是?卡 / 磁碟 IO 會先於 CPU 打滿,這種情況即使優化 CPU 的使?也不能提?整個系統的吞吐量,只能提?磁碟的讀寫速度,增加記憶體??,提升?卡的頻寬來提升整體效能。

? CPU 瓶頸類的程式,則是在儲存和?卡未打滿之前 CPU 占?率提前到達 100%,CPU 忙於各種計算任務,IO 裝置相對則較閒。

?論哪種型別的服務,在資源使?到極限的時候都會導致請求堆積,超時,系統 hang 死,最終傷害到終端?戶。對於分散式的 Web 服務來說,瓶頸還不?定總在系統內部,也有可能在外部。

?計算密集型的系統往往會在關係型資料庫環節失守,?這時候 Web 模組本身還遠遠未達到瓶頸。不管我們的服務瓶頸在哪?,最終要做的事情都是?樣的,那就是流量限制。

常?的流量限制?段

流量限制的?段有很多,最常?的:漏桶、令牌桶兩種:
  • 漏桶是指我們有?個?直裝滿了?的桶,每過固定的?段時間即向外漏?滴?。如果你接到了這滴?,那麼你就可以繼續服務請求,如果沒有接到,那麼就需要等待下?滴?。
  • 令牌桶則是指勻速向桶中新增令牌,服務請求時需要從桶中獲取令牌,令牌的數?可以按照需要消耗的資源進?相應的調整。如果沒有令牌,可以選擇等待,或者放棄。

這兩種?法看起來很像,不過還是有區別的。漏桶流出的速率固定,?令牌桶只要在桶中有令牌,那就可以拿。也就是說令牌桶是允許?定程度的並行的,?如同?個時刻,有 100 個?戶請求,只要令牌桶中有 100 個令牌,那麼這 100 個請求全都會放過去。令牌桶在桶中沒有令牌的情況下也會退化為漏桶模型。

令牌桶
圖:令牌桶