Gubernator高性能分布式限速微服務(wù)
Gubernator 作為微服務(wù)的主要特性是,它為進(jìn)入系統(tǒng)的許多請求創(chuàng)建了一個同步點(diǎn)。在幾微秒內(nèi)接收到的請求可以被優(yōu)化并協(xié)調(diào)成批,從而減少服務(wù)在重載下使用的總帶寬和往返延遲。多個服務(wù)都運(yùn)行在單個主機(jī)上,并且所有服務(wù)都在各自的進(jìn)程中運(yùn)行相同的庫,但它們沒有此功能。
Gubernator 的特性
- Gubernator 在整個集群中均勻地分布速率限制請求,這樣用戶就可以添加更多的節(jié)點(diǎn)來擴(kuò)展系統(tǒng)。
- Gubernator 不依賴于 Memcache 或 Redis 等外部緩存,因此部署時不存在服務(wù)依賴。這使得在諸如 kubernetes 或 nomad 的編排系統(tǒng)中能動態(tài)增長或縮小集群。
- Gubernator 在磁盤上不保存狀態(tài),它的配置是由客戶機(jī)根據(jù)每個請求傳遞給它的。
- Gubernator 提供了對其 API 的 GRPC 和 HTTP 訪問。可以根據(jù)需要限制速率的陪伴服務(wù)運(yùn)行,也可以作為獨(dú)立的服務(wù)運(yùn)行。
- 可以用作庫來實(shí)現(xiàn)特定領(lǐng)域的限速服務(wù)。
- 支持對高吞吐量環(huán)境進(jìn)行定制化的一致速率限制服務(wù)。
- Gubernator 是 俄語中 governor 的英文發(fā)音 ,聽起來也很酷。
示例配置:
rate_limits:
# Scopes the request to a specific rate limit
- name: requests_per_sec
# A unique_key that identifies this instance of a rate limit request
unique_key: account_id=123|source_ip=172.0.0.1
# The number of hits we are requesting
hits: 1
# The total number of requests allowed for this rate limit
limit: 100
# The duration of the rate limit in milliseconds
duration: 1000
# The algorithm used to calculate the rate limit
# 0 = Token Bucket
# 1 = Leaky Bucket
algorithm: 0
# The behavior of the rate limit in gubernator.
# 0 = BATCHING (Enables batching of requests to peers)
# 1 = NO_BATCHING (Disables batching)
# 2 = GLOBAL (Enable global caching for this rate limit)
Gubernator 是無狀態(tài)的,因?yàn)樗恍枰疟P空間來操作。不需要任何配置或緩存數(shù)據(jù)同步到磁盤,這是因?yàn)閷?Gubernator 的每個請求都包含速率限制的配置。
首先,你可能認(rèn)為這對每個請求都是不必要的開銷。然而,實(shí)際上,速率限制配置僅由 4 個 64 位整數(shù)組成。配置由限制、持續(xù)時間、算法和行為組成 (有關(guān)工作原理的詳細(xì)信息,請參閱下面)。正是由于這種簡單的配置,Gubernator 可以用來提供客戶端可以使用的各種速率限制用例。其中一些用例如下:
- 入口限制:典型的基于 HTTP 的 402 多請求類型限制。
- 流量減少:當(dāng) API 處于不佳狀態(tài)時,只拒絕新的或未經(jīng)身份驗(yàn)證的請求。
- 出口限制:用數(shù)百萬條消息轟炸外部 SMTP 服務(wù)器并非易事。
- 隊(duì)列處理:知道何時可以立即處理請求,或者應(yīng)該按照接收請求的順序排隊(duì)和處理請求。
- API 容量管理:對一個集合 API 系統(tǒng)能夠處理的請求總數(shù)設(shè)置全局限制。拒絕或?qū)`反系統(tǒng)正常操作能力的請求進(jìn)行排隊(duì)。
除了上面提到的用例,無配置設(shè)計對微服務(wù)的設(shè)計和部署有重要的影響:
- 部署時不用配置同步。當(dāng)使用 Gubernator 的服務(wù)被部署時,不用預(yù)先部署到 Gubernator 的速率限制配置。
- 使用 Gubernator 的服務(wù)擁有其問題空間的速率極限域模型。這使得 Gubernator 無法獲得領(lǐng)域特定的知識,因此 Gubernator 可以專注于它最擅長的事情——速率限制!
在這些問題之外,下面就從 Gubernator 的工作原理開始,討論更多關(guān)于 Gubernator 的內(nèi)容。
Gubernator 的工作原理
Gubernator 被設(shè)計成一個分布式的對等點(diǎn)集群,它利用了內(nèi)存中所有當(dāng)前活動速率限制的緩存,因?yàn)椴挥脤?shù)據(jù)同步到磁盤。由于大多數(shù)基于網(wǎng)絡(luò)的速率限制持續(xù)時間只有幾秒鐘,因此在重啟或計劃停機(jī)期間丟失內(nèi)存緩存并不是什么大問題。對于 Gubernator,我們選擇性能而不是精度,因?yàn)樵诰彺鎭G失的情況下,一小部分流量在短時間內(nèi) (通常是幾秒鐘) 超過請求是可以接受的。
當(dāng)向 Gubernator 發(fā)出速率限制請求時,將鍵入該請求并應(yīng)用一致的哈希算法來確定哪個對等點(diǎn)將是速率限制請求的所有者。為速率限制選擇單個所有者可以使計數(shù)的原子增量非??欤⑶冶苊饬嗽趯Φ燃褐幸恢碌胤植加嫈?shù)所涉及的復(fù)雜性和延遲。
盡管簡單且性能良好,但是這種設(shè)計可能會受到一大堆請求的影響,因?yàn)橐粋€協(xié)調(diào)器可能要處理成千上萬個請求,而且速度有限。
為了解決這個問題,客戶機(jī)可以請求 Behaviour=BATCHING,它允許對等點(diǎn)在指定的窗口內(nèi)接受多個請求 (缺省值為 500 微秒),并將請求批處理為單個對等點(diǎn)請求,從而極大地減少了通過網(wǎng)絡(luò)向單個 Gubernator 對等點(diǎn)發(fā)送請求的總數(shù)。
為了確保集群中的每個對等點(diǎn)準(zhǔn)確地計算速率限制鍵的正確散列,必須以及時和一致的方式將集群中的對等點(diǎn)列表分發(fā)給集群中的每個對等點(diǎn)。目前,Gubernator 支持使用 etcd 或 kubernetes 端點(diǎn) API 來發(fā)現(xiàn) Gubernator 對等點(diǎn)。
Gubernator 操作
當(dāng)客戶機(jī)或服務(wù)向 Gubernator 發(fā)出請求時,客戶機(jī)將為每個請求提供速率限制配置。然后,速率限制配置與當(dāng)前速率限制狀態(tài)一起存儲在速率限制所有者的本地緩存中。存儲在本地緩存中的速率限制及其配置僅在速率限制配置的指定持續(xù)時間內(nèi)存在。
在持續(xù)時間過期之后,如果在此期間沒有再次請求速率限制,則從緩存中刪除它。對相同名稱和 unique_key 對的后續(xù)請求將在緩存中重新創(chuàng)建配置和速率限制,這個循環(huán)將重復(fù)。另一方面,具有不同配置的后續(xù)請求將覆蓋以前的配置并立即應(yīng)用新配置。
由于 Gubernator 速率限制是由集群中的單個對等點(diǎn)哈希和處理的,所以適用于數(shù)據(jù)中心中的每個請求的速率限制將導(dǎo)致單個對等點(diǎn)處理整個數(shù)據(jù)中心的速率限制請求。
例如,考慮 name=requests_per_datacenter 和 unique_id=us-east-1 的速率限制?,F(xiàn)在,假設(shè)對每個進(jìn)入 us-east-1 數(shù)據(jù)中心的 HTTP 請求都使用這個速率限制向 Gubernator 發(fā)出請求。這可能是每秒數(shù)十萬個請求,甚至可能是數(shù)百萬個請求,這些請求都由集群中的一個對等點(diǎn)哈希并處理。由于這個潛在的可伸縮性問題,Gubernator 引入了一個名為 GLOBAL 的可配置 behavior。
當(dāng)速率限制配置為 behavior=GLOBAL 時,從客戶機(jī)接收到的速率限制請求將不會轉(zhuǎn)發(fā)給擁有它的對等方。相反,它將從接收請求的對等方處理的內(nèi)部緩存中得到響應(yīng)。Hits 速率限制的點(diǎn)擊率將由接收對等點(diǎn)批量處理,并異步發(fā)送到擁有該點(diǎn)擊率的對等點(diǎn),在該對等點(diǎn)上,點(diǎn)擊率將被總計并得出 OVER_LIMIT。然后,擁有節(jié)點(diǎn)的節(jié)點(diǎn)有責(zé)任用速率限制的當(dāng)前狀態(tài)更新集群中的每個節(jié)點(diǎn),這樣,節(jié)點(diǎn)內(nèi)部緩存就會定期從所有者那里獲得最新速率限制狀態(tài)的更新。
Global Behavior 的其他影響
由于 Hits 是批量處理并異步轉(zhuǎn)發(fā)給擁有它的對等點(diǎn)的,所以對客戶機(jī)的即時響應(yīng)將不包括最精確的 remaining 計數(shù)。只有在對所有者對等點(diǎn)的異步調(diào)用完成并且擁有對等點(diǎn)有時間更新集群中的所有對等點(diǎn)之后,該計數(shù)才會得到更新。因此,使用 GLOBAL 允許更大的集群規(guī)模,但要以一致性為代價。如果集群足夠大,使用 GLOBAL 可以增加每速率限制請求的通信量。 GLOBAL 應(yīng)該只用于與傳統(tǒng)的非 GLOBAL 行為不兼容的高容量速率限制。
Gubernator 性能
在我們的生產(chǎn)環(huán)境中,每向我們的 API 發(fā)送一個請求,我們就向 Gubernator 發(fā)送兩個速率限制請求來評估速率限制;一個用于對 HTTP 請求進(jìn)行評級,另一個用于對用戶在特定時間內(nèi)也可以發(fā)送電子郵件的收件人數(shù)量進(jìn)行評級。在這種設(shè)置下,一個 Gubernator 節(jié)點(diǎn)每秒處理超過 2000 個請求,大多數(shù)批量響應(yīng)在 1 毫秒內(nèi)返回。
轉(zhuǎn)發(fā)給擁有節(jié)點(diǎn)的對等請求通常在 30 微秒內(nèi)響應(yīng)。
- NOTE
- The
- above
- graphs
- only report
- the slowest
- request
- within the
- 1 second
- sample
- time. So
- you are
- seeing the
- slowest
- requests
- that
- Gubernator
- fields to
- clients.
由于許多面向公眾的 API 都是用 python 編寫的,所以我們在一個節(jié)點(diǎn)上運(yùn)行許多 python 解釋器實(shí)例。這些 python 實(shí)例將本地請求轉(zhuǎn)發(fā)給 Gubernator 實(shí)例,然后 Gubernator 實(shí)例將請求批處理并轉(zhuǎn)發(fā)給擁有節(jié)點(diǎn)的節(jié)點(diǎn)。
Gubernator 允許用戶選擇非批處理行為,這將進(jìn)一步減少客戶機(jī)速率限制請求的延遲。但是,由于吞吐量需求,我們的生產(chǎn)環(huán)境使用默認(rèn)的 500 微秒窗口使用 Behaviour=BATCHING。在生產(chǎn)中,我們觀察到在 API 使用高峰期間,批處理大小為 1000。其他不具有相同高流量需求的用戶可以禁用批處理,并以吞吐量為代價降低延遲。
Gubernator 開發(fā)庫
如果使用 Golang,可以使用 Gubernator 作為開發(fā)庫。這對你希望在頂部實(shí)現(xiàn)一個公司特有模型的速率限制服務(wù)時非常有用。我們在 Mailgun 內(nèi)部有一項(xiàng)名為“ratelimits”的服務(wù),專門跟蹤每個賬戶的限額。通過這種方式,你可以利用 Gubernator 的強(qiáng)大功能和速度,同時可以分層業(yè)務(wù)邏輯,并將特定領(lǐng)域的問題集成到速率限制服務(wù)中。
性能表現(xiàn):
下面是一個單節(jié)點(diǎn)每秒 2000 個請求的測試結(jié)果:
轉(zhuǎn)發(fā)給擁有節(jié)點(diǎn)的對等請求通常在30微秒內(nèi)響應(yīng)
