iT邦幫忙

2026 iThome 鐵人賽

DAY 13
0
Build on Google AI

單鐵的人生如履薄冰!AI 教練 APP 30天開發旅程,你說能走到最後嗎?系列 第 13

Day 13 | 上線驗證與流量防護:Cloud Run 擴展限制與 API Rate Limiting 實戰

  • 分享至 

  • xImage
  •  

前言:別讓自動擴展變成破產機器:實作 Cloud Run 雙重防護

Cloud Run 的強大之處在於「無極限自動擴展 (Autoscaling)」,但若了解的讀者肯定知道,帳單上費用名列前茅的總會出現Gemini API 的影子,若遭遇惡意流量狂刷,幾百台機器瞬間開出來,月底帳單會令人崩潰。為了保護 Gemini API 額度與錢包,我們從「基礎設施層」與「應用程式層」進行雙重防護。

觀念解說:雙重防護的架構思維

在雲端架構中,防禦必須是多層次的:

  • 基礎設施層 (Cloud Run):負責從最外圍阻擋,避免無限制開出容器機器,直接斬斷費用飆升的源頭。
  • 應用程式層 (Spring Boot):負責精細的邏輯控制,針對單一 IP 進行「限流 (Rate Limiting)」,避免單一惡意使用者霸佔系統資源。

動手實作 1:基礎設施層——設定 Cloud Run 最大執行個體

在 Google Cloud CLI 執行更新指令,將 kakeru-ai-api 服務硬性限制最多開出 3 台機器:

gcloud run services update kakeru-ai-api \
    --max-instances=3 \
    --region=asia-east1
  • 防護機制:當爆發萬級請求時,最多僅 3 台處理,排隊逾時直接由 Cloud Run 拒絕 (HTTP 503),從根本截斷機器數量飆高導致的費用。

動手實作 2:應用程式層——Spring Boot API 限流實作

1. 引入 Bucket4j 依賴

pom.xml 中加入 Bucket4j 套件,這是一個基於 Token Bucket 演算法的輕量級限流工具:

<dependency>
    <groupId>com.bucket4j</groupId>
    <artifactId>bucket4j-core</artifactId>
    <version>8.3.0</version>
</dependency>

2. 撰寫核心 Filter 程式碼

重點在於:Token Bucket (5次/分鐘) 加上 X-Forwarded-For 真實 Client IP 解析

@Component
public class RateLimitFilter extends OncePerRequestFilter {

    private final Map<String, Bucket> cache = new ConcurrentHashMap<>();

    // 1. Bucket 規則:容量 5 個,每分鐘補充 5 個 Token
    private Bucket createNewBucket() {
        Bandwidth limit = Bandwidth.classic(5, Refill.greedy(5, Duration.ofMinutes(1)));
        return Bucket.builder().addLimit(limit).build();
    }

    // 2. 拆解 Cloud Run Reverse Proxy 轉發的真實 Client IP
    private String extractClientIp(HttpServletRequest request) {
        String xForwardedFor = request.getHeader("X-Forwarded-For");
        if (xForwardedFor != null && !xForwardedFor.isBlank()) {
            return xForwardedFor.split(",")[0].trim(); // 取第一個原始 Client IP
        }
        return request.getRemoteAddr();
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {

        if (!request.getRequestURI().startsWith("/api/")) {
            filterChain.doFilter(request, response);
            return;
        }

        String ip = extractClientIp(request);
        Bucket bucket = cache.computeIfAbsent(ip, k -> createNewBucket());

        // 3. 嘗試消耗 1 個 Token,耗盡時回傳 HTTP 429
        if (bucket.tryConsume(1)) {
            filterChain.doFilter(request, response);
        } else {
            response.setStatus(HttpStatus.TOO_MANY_REQUESTS.value());
            response.setContentType("application/json;charset=UTF-8");
            response.getWriter().write("{\"error\": \"請求過於頻繁,請稍後再試。教練需要休息!\"}");
        }
    }
}

動手實作 3:自動化測試與 curl 實戰驗證

1. 自動化測試精華

使用 Spring Boot MockMvc 驗證前 5 次 200 OK、第 6 次 429 攔截:

@Test
@DisplayName("測試同一 IP 前 5 次請求成功 (200),第 6 次請求觸發限流 (429)")
void rateLimit_ExceedFiveRequests_Returns429() throws Exception {
    String clientIp = "192.168.1.100";

    // 前 5 次:HTTP 200 OK
    for (int i = 1; i <= 5; i++) {
        mockMvc.perform(get("/hello").header("X-Forwarded-For", clientIp))
                .andExpect(status().isOk());
    }

    // 第 6 次:HTTP 429 Too Many Requests
    mockMvc.perform(get("/hello").header("X-Forwarded-For", clientIp))
            .andExpect(status().isTooManyRequests())
            .andExpect(jsonPath("$.error").value("請求過於頻繁,請稍後再試。教練需要休息!"));
}
  • 驗證結果:執行 mvn test 全部 10 項單元與整合測試通過 (BUILD SUCCESS)。

2. curl 壓測與真實回應

打開終端機執行連續請求測試腳本:

for i in {1..10}; do
  curl -i -X POST https://[Cloud-Run-URL]/generate \
  -H "Content-Type: application/json" \
  -d '{"targetRace":"全馬","currentLevel":"初學者"}'
done
  • 第 1~5 次回應HTTP/2 200 OK (成功取得 AI 課表)
  • 第 6 次回應
HTTP/2 429 
content-type: application/json;charset=UTF-8

{"error": "請求過於頻繁,請稍後再試。教練需要休息!"}

踩坑與避雷指南

  • X-Forwarded-For 陷阱:經過 Proxy 或 Load Balancer 時,request.getRemoteAddr() 可能取得的是代理層 IP,因此需要透過可信任的 X-Forwarded-For 取得 Client IP。不要無條件相信 Client 自己提供的 Header。
  • Cloud Armor vs. Bucket4j:Cloud Armor 屬於雲端網路層的流量防護,Bucket4j 則是在 Spring Boot 應用程式內進行限流。MVP 或學習階段可以先使用 Bucket4j;正式環境則可依需求考慮將流量防護往 Cloud Armor 等基礎設施層移動。

今日總結與明日預告

今天完成了 Day 13 的雙重流量防護後,KAKERU AI 教練 API 已經能在 Cloud Run 上穩定運作,並具備基礎的防禦機制,後端 API 的開發階段到此正式告一段落。

系統開發流程中,為了減少流程開發的步驟,肯定少不了自動化部署!!不過,為了優先驗證系統的整體可行性,打算先讓產品具備可操作的前端介面,藉此來測試前後端的真實串接狀況。因此,後端與前端的 CI/CD 自動化流水線(GitHub Actions 與 EAS Build),將會安排在系列文最後的 DevOps 階段一併實作。

明天(Day 14),專案將進入下個階段!!「前端開發與 AI 互動」。我們將暫停後端純文字互動的 Spring Boot 的開發,切換至 TypeScript 與 React Native,開始建立 KAKERU 跨平台手機 APP 的基礎環境與介面。明天見!


上一篇
Day 12 | 把 Docker Image 戴上手銬!部署 Cloud Run 與安全金鑰管理
系列文
單鐵的人生如履薄冰!AI 教練 APP 30天開發旅程,你說能走到最後嗎?13
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言