去年雙 11 凌晨一點,一家做保健食品的客戶打電話來,說網站每秒進兩千單就掛掉。我們上去看,瓶頸不在 Web 伺服器,是 MySQL 的 products 表被 SELECT stock FROM products WHERE id=? 打爆——每張訂單頁刷新都查一次庫存。加了一行 Redis 快取,撐到結束都沒再倒。
大流量架構的核心不是堆機器,是讓每一層只做它該做的事。以下是我們在電商與票務專案中反覆驗證的取捨。
從邊緣往內削減請求
請求每往內走一層,成本就高十倍。所以策略是:能在 CDN 擋掉的,不要進到應用層;能在 Redis 解決的,不要碰資料庫。
CDN 與邊緣快取:商品頁、活動頁這類「對所有人都一樣」的內容,設 Cache-Control: public, max-age=60 就能讓 Cloudflare 或 CloudFront 吃掉九成流量。台灣中小企業常犯的錯,是把登入狀態塞進 HTML 渲染,導致整頁不能快取——拆出來用 JS 在前端組裝就好。
應用層快取:庫存、價格、會員等級這些讀多寫少的資料,放 Redis。重點是快取失效策略——我們偏好 write-through(寫資料庫時同步更新快取),而不是 cache-aside 配 TTL,因為後者在尖峰期容易出現 thundering herd:上千個請求同時發現快取過期,全部打到資料庫。
# write-through 範例
def update_stock(product_id, qty):
with db.transaction():
db.execute("UPDATE products SET stock=%s WHERE id=%s", qty, product_id)
redis.set(f"stock:{product_id}", qty, ex=3600)
訊息佇列削峰:下單流程中,扣庫存要即時,但發送通知、寫入資料倉儲、更新推薦模型都可以丟到 Kafka 或 RabbitMQ 慢慢處理。一張訂單同步邏輯從 800ms 壓到 120ms,是這樣來的。
資料庫分片與無狀態服務
當單一 MySQL 已經垂直擴到 64 核還是寫不動,就該分片。但分片是不可逆的痛——一旦切下去,跨片 JOIN、跨片交易都要靠應用層處理。
我們的建議:先窮盡讀寫分離與索引優化。多數台灣電商的「資料庫撐不住」其實是慢查詢沒抓出來,加個 pt-query-digest 看 top 10,通常能擠出三倍效能。真要分片,依 user_id 或 tenant_id hash 是最穩的,避免用時間欄位(會造成熱點)。
無狀態服務是水平擴展的前提。Session 別放記憶體,丟 Redis 或用 JWT;檔案上傳別寫本機磁碟,直接傳 S3。這樣 Kubernetes 才能在尖峰時從 4 個 Pod 自動長到 40 個。
可觀測性是最常被低估的一塊。沒有 metrics、tracing、log 三件套,出事時你只能猜。最低門檻:Prometheus + Grafana 監控 QPS、P99 latency、錯誤率,再用 OpenTelemetry 串起跨服務追蹤。一條慢請求從哪一段卡住,三十秒內要能定位。
我們的觀察
撐住流量的關鍵不是用了多潮的技術,而是想清楚每個請求的生命週期。我們看過太多客戶花錢買了 Kubernetes 和 Kafka,結果還是因為一條沒加索引的 SQL 倒站。先把監控做好、把慢查詢殺掉、把可快取的快取掉——這三件事做完,多數中小企業的流量問題就解了一半。剩下那一半,再談分片與微服務也不遲。
