~/blog/wordpress-api-rate-limit-exponential-backoff-retry-guide-2.md
API 串接與系統整合 · 2026 / 01 / 31

API 請求總是 429?用「指數退讓」演算法,優雅解決 WordPress 串接被鎖問題

Eric — 浪花科技創辦人 / AI 架構師
Eric
浪花科技創辦人 · AI 架構師
API 請求總是 429?用「指數退讓」演算法,優雅解決 WordPress 串接被鎖問題
目錄 table-of-contents.md

滿滿的紅色 429 Too Many Requests,差點讓我把剛喝進去的黑咖啡噴出來——這是我最近幫客戶重構一個串接 OpenAI 和第三方 ERP 的大型 WooCommerce 專案時,打開 Error Log 看到的景象。更精彩的是,原本的程式碼處理方式竟然是直接在迴圈裡寫了 sleep(1) 然後無限重試。這就像是你去排隊買雞排,老闆說現在沒貨請稍等,結果你每隔一秒鐘就對老闆大吼一次:「好了沒?好了沒?」,老闆不把你轟出去才怪。

在 WordPress 開發中,API 串接(API Integration)是家常便飯,但如何優雅地處理 Rate Limit(速率限制) 卻是區分「寫出來能動」與「企業級架構」的分水嶺。今天這篇文章,我要把這幾年踩坑累積的經驗,特別是關於「指數退讓(Exponential Backoff)」與重試機制的實作細節,毫無保留地分享給各位開發者。

什麼是 API Rate Limit?為什麼你會被鎖?

簡單來說,API Rate Limit 是服務提供商為了保護自家伺服器不被單一用戶癱瘓所設下的防護機制。當你在短時間內發送過多的請求(Request),超出了對方的允許範圍(例如:每分鐘 60 次),伺服器就會回傳 HTTP 狀態碼 429 (Too Many Requests)

這時候,如果你只是單純地「失敗就重試」,通常會導致以下後果:

  • 帳號被封鎖: 許多 API(如 LINE Messaging API 或 Facebook Graph API)如果偵測到你在被限流後還持續暴力請求,會直接暫時或永久封鎖你的 IP 或 API Token。
  • 伺服器資源耗盡: 在 PHP (WordPress) 的環境下,如果你在 HTTP 請求中使用 sleep() 等待重試,這會佔用 PHP Worker 的執行緒。如果有大量請求同時發生,你的網站前台會因為無 Worker 可用而卡死,造成 502 Bad Gateway。
  • 業務邏輯中斷: 訂單沒同步、信件沒寄出,客戶氣炸。

告別暴力迴圈:什麼是「指數退讓」(Exponential Backoff)?

要解決這個問題,業界標準的做法是實作指數退讓(Exponential Backoff)策略。這聽起來很數學,其實邏輯很簡單:「每次失敗後,等待的時間都要加倍。」

假設我們設定基礎等待時間為 1 秒:

  • 第一次失敗:等待 1 秒後重試。
  • 第二次失敗:等待 2 秒後重試 (1 * 2)。
  • 第三次失敗:等待 4 秒後重試 (2 * 2)。
  • 第四次失敗:等待 8 秒後重試 (4 * 2)。

這樣的機制可以讓你的程式在系統壅塞時自動「慢下來」,展現出禮貌,大大降低被 API 提供商視為惡意攻擊的機率。

WordPress 實戰程式碼:打造優雅的 API Wrapper

在 WordPress 中,我們通常使用 wp_remote_requestwp_remote_getwp_remote_post 來發送請求。我們不應該直接呼叫這些函數,而是要封裝一層專門處理重試邏輯的 Wrapper。

以下是我常用的 Helper Function 範本,支援經典編輯器與區塊編輯器,直接貼在 functions.php 或你的外掛類別中即可使用:

/**
 * 發送 HTTP 請求並實作指數退讓重試機制
 *
 * @param string $url 請求的 URL
 * @param array $args wp_remote_request 的參數
 * @param int $max_retries 最大重試次數
 * @return array|WP_Error
 */
function roamer_remote_request_with_retry( $url, $args = [], $max_retries = 3 ) {
    $current_retry = 0;
    
    while ( $current_retry = 500 ) {
                // 伺服器端錯誤 (500, 502, 503),通常稍後會恢復
                $should_retry = true;
            } else {
                // 200 OK 或 4xx 用戶端錯誤 (400, 401, 404),不重試,直接回傳結果
                return $response;
            }
        }

        if ( $should_retry ) {
            $current_retry++;
            
            // 如果已經達到最大重試次數,則拋出錯誤或回傳最後一次的回應
            if ( $current_retry >= $max_retries ) {
                return $response;
            }

            // 計算等待時間 (指數退讓)
            // 公式:2 ^ (重試次數 - 1) * 基礎秒數
            $sleep_seconds = pow( 2, $current_retry - 1 );
            
            // 檢查 API 是否有回傳 Retry-After Header
            // 許多 API (如 Shopify, Stripe) 會明確告訴你該等多久
            $retry_after = wp_remote_retrieve_header( $response, 'retry-after' );
            if ( ! empty( $retry_after ) && is_numeric( $retry_after ) ) {
                $sleep_seconds = intval( $retry_after );
            }

            // 加入 "Jitter" (隨機抖動)
            // 避免多個併發請求在同一時間點同時重試,造成「雷鳴效應」
            $jitter = rand( 0, 1000 ) / 1000; // 0 ~ 1秒之間的隨機數
            $sleep_seconds += $jitter;

            // 為了不卡死 PHP Worker,這裡的 sleep 要謹慎使用
            // 在長執行時間的背景任務 (Cron/CLI) 中較為安全
            // 若是前端請求,建議最多重試 1 次或改用 Queue
            sleep( (int) $sleep_seconds );
            
            // 紀錄 Log (建議使用 WC_Logger 或其他 Log 機制)
            error_log( "API Retry {$current_retry}/{$max_retries} for URL: {$url}. Waiting {$sleep_seconds}s" );
        }
    }

    return $response;
}

程式碼解析:魔鬼藏在細節裡

  1. Retry-After Header 的優先權: 很多工程師會忽略這一點。規範的 API 在回傳 429 時,通常會在 Header 帶上 Retry-After,告訴你精確的等待秒數。如果 API 已經告訴你要等 60 秒,你的指數退讓只等 2 秒是沒有意義的,所以必須優先採用 API 的建議。
  2. Jitter (隨機抖動) 的重要性: 這是我在處理高併發系統時學到的教訓。如果你的系統同時發出了 100 個請求並同時失敗,若大家都準時在 2 秒後重試,那第 2 秒的時候伺服器會再次被這 100 個請求瞬間打垮(這稱為 Thundering Herd Problem,雷鳴效應)。加入一個微小的隨機時間(Jitter),可以把這些重試請求「錯開」,大幅提升成功率。

進階思考:同步 vs 非同步 (Sync vs Async)

雖然上面的程式碼解決了重試邏輯,但 Eric 必須提醒你一個嚴重的架構問題:PHP 是單執行緒且同步阻斷的。

如果使用者在結帳頁面按下「送出」,而你的 API 剛好遇到 429 錯誤,程式開始 sleep(2)sleep(4)... 這意味著使用者的瀏覽器會在那邊轉圈圈轉個 6 秒以上。這對使用者體驗(UX)是毀滅性的打擊,而且容易導致瀏覽器 timeout。

最佳解法:使用 Action Scheduler

對於需要高可靠性且可能被限流的 API 操作(例如同步訂單到 HubSpot 或 Salesforce),我強烈建議不要在當下重試,而是將任務推送到背景佇列(Queue)。

在 WordPress 生態系中,最棒的工具莫過於 WooCommerce 內建的 Action Scheduler。當遇到 429 錯誤時,你不是 sleep,而是安排一個 Action 在 5 分鐘後執行:

if ( $response_code === 429 ) {
    // 安排在 5 分鐘後重試
    as_schedule_single_action( 
        time() + 300, 
        'roamer_retry_api_sync', 
        [ 'order_id' => $order_id ] 
    );
    return;
}

這樣做,前端使用者可以瞬間完成操作,而繁重的重試與等待邏輯則交由背景排程器優雅地處理。

總結

處理 API Rate Limit 不僅僅是為了讓程式不報錯,更是為了保護你的伺服器資源以及提供流暢的使用者體驗。從最基礎的指數退讓演算法,到加入 Jitter 防止雷鳴效應,再到進階的使用 Action Scheduler 進行非同步處理,這才是資深 WordPress 工程師該有的思維。

開發系統時,永遠要假設外部服務是不可靠的。做好防禦性程式設計(Defensive Programming),你的週末才能安穩地度過,不用半夜起來修 Bug。

相關閱讀

如果你對 WordPress 的 API 串接與自動化架構有興趣,推薦你閱讀以下這幾篇我精選的技術文章,這能幫你建立更完整的全端思維:

遇到棘手的 API 串接難題嗎?

無論是複雜的 ERP 整合、高併發的資料同步,或是需要客製化的 API 架構,浪花科技的工程團隊都能為您提供最專業的技術支援。別讓技術瓶頸限制了您的業務發展。

立即聯繫 Eric 諮詢技術方案
// FAQ

常見問題

API 回傳 HTTP 429 是什麼意思?
429 Too Many Requests 代表你在短時間內發送的請求超出 API 服務商設定的速率限制(Rate Limit)。這是服務商為了保護伺服器不被單一用戶癱瘓所設的防護機制。若在被限流後仍持續暴力重試,可能導致 IP 或 API Token 被暫時甚至永久封鎖。
什麼是指數退讓(Exponential Backoff)?
指數退讓是處理 API 限流的業界標準重試策略,核心邏輯是「每次失敗後,等待的時間都要加倍」。例如基礎等待 1 秒,失敗後依序等待 1、2、4、8 秒再重試。這能讓程式在系統壅塞時自動慢下來,大幅降低被 API 服務商視為惡意攻擊的機率。
重試機制為什麼要加入 Jitter(隨機抖動)?
若大量請求同時失敗又在相同秒數一起重試,會在同一瞬間再次打垮伺服器,這稱為雷鳴效應(Thundering Herd Problem)。在等待時間上加入一個微小的隨機值(Jitter),可以把這些重試請求錯開,避免同時湧入,大幅提升重試成功率。
處理 Retry-After Header 時該怎麼做?
規範的 API 回傳 429 時通常會在 Header 帶上 Retry-After,明確告訴你該等待的秒數。應優先採用這個值,而非只用自己計算的指數退讓秒數。如果 API 要求等 60 秒,你只等 2 秒就重試是沒有意義的。
為什麼在前端請求中直接用 sleep 重試是危險的?
PHP 是單執行緒且同步阻斷的,在 HTTP 請求中用 sleep 等待會佔用 PHP Worker 執行緒,大量請求同時發生時會因無 Worker 可用而導致網站卡死或 502 Bad Gateway。對於可能被限流的關鍵操作,建議改用 WooCommerce 的 Action Scheduler 把任務推送到背景佇列重試,讓前端使用者能瞬間完成操作。
~/roamer-tech/newsletter // FREE
// newsletter

訂閱免費電子報

把 AI 自動化、企業系統設計與 WordPress / Laravel 開發的真實案例和可直接照做的技巧,整理成電子報寄給你。只寄精選內容、不灌垃圾信,一鍵就能退訂。

$
// final.exec()

準備好讓你的網站開始為你工作了嗎?