GitHub 限制了你的應用程式能發送的 REST API 請求數量。 當你超過限制時,GitHub 會以 429 或 403 狀態拒絕你的請求,直到限制重置或等待時間結束。 GitHub 有兩種限制:主要限制每小時請求數,以及防止突發的次級限制。 如果你的應用程式在速率限制期間持續發送請求,GitHub 可能會禁止你的整合。 欲了解更多資訊,請參閱 REST API 的速率限制。
GitHub 的速率限制長什麼樣子
| Limit | 價值 | 當你檢視它時 |
|---|---|---|
| 主要、未經驗證 | 每個 IP 位址每小時 60 次請求 |
403 或 429,且 x-ratelimit-remaining 是 0 |
| 主要個人存取權杖 | 每小時5,000個請求 | 同上 |
主要項目,GITHUB_TOKEN在 GitHub Actions 中 |
每個儲存庫每小時 1,000 筆請求 | 同上 |
| 次要 | 例如,同時請求不超過 100 筆,REST 端點每分鐘 900 點數,約每分鐘 80 筆內容產生請求 |
403 或 429,並顯示錯誤訊息。
retry-after 可能存在。 |
GitHub 可以在不事先通知的情況下更改次要限制,而且也無法查看你離這些限制還有多近。
每個回應都會有標頭告訴你你在主要限制上的進度:
| Header | 它告訴你的 |
|---|---|
x-ratelimit-limit |
每小時最多可傳送的請求數 |
x-ratelimit-remaining |
你目前還剩多少請求 |
x-ratelimit-used |
你在目前時間區間內已傳送多少個請求 |
x-ratelimit-reset |
當視窗重置時,以 UTC Unix 紀元秒數計算 |
x-ratelimit-resource |
請求會計入哪個限制 |
如何處理 GitHub 的速率限制
- 分辨速率限制與權限錯誤。 當你的令牌缺乏存取權時,GitHub 也會回傳
403。 如果回應沒有retry-after、x-ratelimit-remaining不是0,且訊息中沒有提到速率限制,那就是權限問題。 不要重試它。 - 先追蹤
retry-after。 如果標頭存在,就等那麼多秒。 - 否則,就等待重設完成。 如果
x-ratelimit-remaining是0,請不要重試,直到x-ratelimit-reset顯示的時間。 - 否則,至少等一分鐘。 如果是沒有任何標頭的次要限制,GitHub 會要求你至少等待 1 分鐘,且每次重試失敗後再等更久。 在設定次數的重試後停止並引發錯誤。
-
在用完之前放慢速度。 使用
x-ratelimit-remaining和x-ratelimit-reset調整你的請求節奏。 不要用精確的剩餘計數來建立邏輯,因為 GitHub 可以改變限制。x-ratelimit-*標頭才是權威來源,不是GET /rate_limit端點。
async function githubWaitMs(response, attempt) {
if (response.status !== 403 && response.status !== 429) {
return null;
}
const retryAfter = response.headers.get('retry-after');
if (retryAfter) {
return Number(retryAfter) * 1000;
}
if (response.headers.get('x-ratelimit-remaining') === '0') {
const resetMs = Number(response.headers.get('x-ratelimit-reset')) * 1000;
return Math.max(resetMs - Date.now(), 0);
}
const { message = '' } = await response.clone().json().catch(() => ({}));
if (response.status === 429 || /rate limit/i.test(message)) {
return 60_000 * 2 ** attempt;
}
// A 403 without rate limit signals is a permission problem: don't retry
return null;
}
當函式回傳一個數字時,呼叫者會重試,並在嘗試幾次後停止。
如何測試你的應用程式是否能處理 GitHub 的速率限制
開發時很少會遇到 GitHub 的速率上限。 你只要發送幾個請求,每小時 5,000 個就感覺像是用不完。 所以你測試速率限制處理的方式,決定了你是否比使用者先發現錯誤。
| Approach | 你發現的 | 你錯過什麼 |
|---|---|---|
| 等待正式環境 | 實際失敗 | 所有內容,直到使用者點選它 |
| 在測試中模擬 API,或讓你的程式代理寫出模擬 | 你的重試分支是否執行 | GitHub 的真實標頭和錯誤回應本文,以及你 SDK 的重試政策。 你的應用程式也需要一個只供測試使用的開關來連線到模擬環境。 |
| 呼叫真正的 API,直到它限制了你 | 實際行為 | 它最多會用到 5,000 次請求,且你無法按需觸發次級限制,且你的整合可能遭到封鎖 |
| 攔截應用程式的真實流量,並按需返回速率限制回應 | 真實網址、真實的 SDK 與重試政策,以及 GitHub 自有的標頭和錯誤格式 | 你的應用程式中的任何內容都不會改變,所以它不會在隔離狀態下測試你的程式碼。 那個就留給你的單元測試吧。 |
在你的應用程式中試試看
Dev Proxy 會攔截你應用程式對 api.github.com 的請求,並回傳 GitHub 式的速率限制回應,而你的應用程式則持續呼叫真實的 URL。
github-rate-limiting 預設設定會將你的請求計入每小時 60 次的限制額度,傳送 x-ratelimit-* 標頭,並在你用完額度時回傳 429,並附帶 API rate limit exceeded。在那之前,你的請求會被送到 GitHub,並計入你的實際限制。
下載預設集,並用它啟動 Dev Proxy:
devproxy config get github-rate-limiting
devproxy --config-file "~dataFolder/configs/github-rate-limiting/.devproxy/devproxyrc.json"
要測試次要限制,建議改為從同一個資料夾使用 devproxyrc-secondary.json 啟動 Dev Proxy。 它會隨機回傳次級速率限制retry-after,並帶有429標頭。
然後照常執行你的應用程式,觀察它的表現。 若要安裝 Dev Proxy,請參見 設定 Dev Proxy。