跳至內容
THE GUILD
0%
服務 產品 招募 關於我們 部落格 常見問題 聯繫我們
一個React useEffect Hook如何搞垮了Cloudflare:前端開發的關鍵教訓

在2025年9月,網際網路最關鍵的基礎設施公司之一經歷了一次故障,這不是由複雜的駭客、大規模DDoS攻擊或伺服器故障引起的。相反,Cloudflare——這家經常抵禦太位元級攻擊並維持數百萬網站運行的公司——被一個許多初級開發者在第一週就學會避免的基本React程式設計錯誤搞垮了。

令人震驚的現實

諷刺幾乎是喜劇性的:Cloudflare成功抵禦了每秒7.3太位元的DDoS攻擊,卻被一個依賴陣列不當的React useEffect hook搞下線了。這不是什麼精英國家級駭客小隊——而是一個經典的前端開發反模式,創造了無限的API呼叫循環。

來源:本文引用的技術細節和時間線基於Cloudflare的官方事件報告:「深入了解Cloudflare 2025年9月12日控制面板和API故障」

理解技術錯誤

問題程式碼模式

問題源於這個常見的React模式:

// 問題程式碼的簡化版本
function Dashboard() {
  const params = { // 這個物件在每次渲染時都會被重新創建
    organizationId: user.orgId,
    filters: currentFilters
  };

  useEffect(() => {
    // 這個函數呼叫API
    fetchDashboardData(params);
  }, [params]); // ← 問題在這裡

  return "Dashboard rendered";
}

為什麼這會創造無限迴圈

useEffect hook使用淺層比較來比較依賴項。這是發生的事情:

  1. 元件渲染 → 創建新的params物件
  2. useEffect執行 → 獲取資料,可能更新狀態
  3. 狀態更新觸發重新渲染 → 創建另一個新的params物件
  4. React比較依賴項params引用已更改
  5. useEffect再次執行 → 回到步驟2,創造無限迴圈

為什麼params物件每次都會被重新創建?

在JavaScript中,像{ organizationId: user.orgId, filters: currentFilters }這樣的物件字面量每次執行時都會在記憶體中創建一個新物件。即使裡面的值相同,物件引用也是不同的。

// 每次這個函數執行時,都會創建一個新物件
const params = { organizationId: user.orgId, filters: currentFilters };

// 這等同於:
const params = new Object();
params.organizationId = user.orgId;
params.filters = currentFilters;

記憶體引用比較:

// 這些物件有相同的內容但不同的記憶體引用
const obj1 = { name: "John" };
const obj2 = { name: "John" };
console.log(obj1 === obj2); // false - 不同的記憶體位置

// 只有相同引用才返回true
const obj3 = obj1;
console.log(obj1 === obj3); // true - 相同的記憶體引用

React的useEffect使用Object.is()(類似於===)來比較依賴項。由於每次渲染都會創建新的params物件,React認為依賴項已更改,即使裡面的值可能相同。

即使params物件包含相同的值,React把它看作「不同的」,因為它每次都是記憶體中的新物件引用。

問題的規模

根據Cloudflare的事件報告,這個簡單的錯誤導致:

  • 單個控制面板會話每分鐘數千次API呼叫
  • 當乘以所有使用者時API完全過載
  • 整個基礎設施的連鎖故障
  • 影響數百萬網站的全球故障

正確的解決方案

解決方案1:記憶化依賴項

import { useMemo, useEffect } from 'react';

function Dashboard() {
  const params = useMemo(() => ({
    organizationId: user.orgId,
    filters: currentFilters
  }), [user.orgId, currentFilters]); // 只有當這些改變時才重新創建

  useEffect(() => {
    fetchDashboardData(params);
  }, [params]);

  return "Dashboard rendered";
}

解決方案2:分離依賴項

function Dashboard() {
  useEffect(() => {
    const params = {
      organizationId: user.orgId,
      filters: currentFilters
    };
    fetchDashboardData(params);
  }, [user.orgId, currentFilters]); // 直接依賴項

  return "Dashboard rendered";
}

解決方案3:對函數使用useCallback

import { useCallback, useEffect } from 'react';

function Dashboard() {
  const fetchData = useCallback(async () => {
    const params = {
      organizationId: user.orgId,
      filters: currentFilters
    };
    await fetchDashboardData(params);
  }, [user.orgId, currentFilters]);

  useEffect(() => {
    fetchData();
  }, [fetchData]);

  return "Dashboard rendered";
}

更廣泛的工程教訓

1. 程式碼審查失敗

這個明顯的無限迴圈是怎麼進入生產環境的?這個事件突顯了幾個工程流程失敗:

  • 本地開發測試應該立即顯示數千個網路請求
  • 程式碼審查流程應該捕捉基本的React反模式
  • 預演環境應該複製生產負載模式
  • 監控系統應該對異常的API使用模式發出警報

2. 驚群問題

當Cloudflare試圖通過清除使用者會話來修復問題時,他們無意中創造了一個「驚群」問題——服務恢復時數百萬使用者同時重新認證,導致第二次故障。

3. 速率限制和斷路器

這個事件揭示了Cloudflare的內部API缺乏適當的:

  • 速率限制以防止濫用
  • 斷路器以在負載下優雅失敗
  • 自動回滾機制用於問題部署

預防策略

對於開發者

  1. 使用ESLint規則exhaustive-deps來捕捉依賴項問題
  2. 安裝React Developer Tools來監控元件重新渲染
  3. 添加網路監控在開發期間捕捉異常的API模式
  4. 實踐防禦性程式設計配合適當的錯誤邊界

對於工程團隊

  1. 實施漸進式發布而不是即時全球部署
  2. 設置適當的監控針對API使用模式
  3. 建立程式碼審查清單針對常見的React反模式
  4. 創建負載測試環境模擬真實使用情況

必要的ESLint配置

{
  "extends": ["plugin:react-hooks/recommended"],
  "rules": {
    "react-hooks/exhaustive-deps": "error"
  }
}

關鍵的學習時刻

這個事件有力地提醒我們,基本的程式設計錯誤可以產生巨大的全球影響。這次故障影響了全球數百萬網站和無數企業,全都因為一個本可以通過適當工具和流程捕捉的錯誤。

正如ThePrimeagen,一位知名且著名的科技YouTuber,對這個事件評論說:「我們都犯過這個錯誤——我只是慶幸我在開發中發現了我的,而不是在生產中。」

關鍵要點

  1. 基礎知識很重要 — 即使在企業規模,基本的程式設計原則也是關鍵的
  2. 工具是必不可少的 — ESLint、React DevTools和適當的監控可以防止這些問題
  3. 流程失敗會放大技術失敗 — 多個安全網同時失效
  4. 漸進式部署拯救生命 — 即時全球發布對關鍵基礎設施來說很危險
  5. React的力量需要責任 — 框架的靈活性要求有紀律的開發實踐

展望未來

Cloudflare此後已實施:

  • Argo Rollouts用於自動部署回滾
  • 增強的API使用模式監控
  • 更好的速率限制和斷路器模式
  • 改進的前端變更程式碼審查流程

這個事件有力地提醒我們,在這個互聯的世界中,即使是最小的程式碼變更也可能產生巨大的全球影響。無論你是在建立簡單網站還是管理關鍵基礎設施,理解React基礎知識和實施適當的工程流程不僅僅是好的實踐——它對網際網路穩定性至關重要。

下次你寫useEffect hook時,記住:Cloudflare的工程師們可能也在仔細檢查他們的依賴陣列。