昨天說唯一炸過的全在同步那一欄——今天把兩場仗都講完。
先講批次側唯一的重傷,它不是被流量打的,是被一筆資料毒的。
當時系統還沒完全正式上線——有些測試驗收還沒做完,但主播跟原本第三方軟體公司的合約提早談崩了,系統直接被推上實戰:上線的時間表,從來不是我們定的。然後某場直播,詭異的事發生了:留言看起來很多,下單量只有小貓兩三隻。主播氣瘋。
兇手是一行邊界:batch 200 裡只要有一筆留言 parsing 意外失敗,整批放棄——一則怪留言,拖著 199 個無辜的訂單陪葬。串流處理的世界管這叫 poison pill(毒藥訊息),而我們的版本特別痛在爆炸半徑:失敗的粒度是「批」,不是「筆」。症狀還極具欺騙性:ingestion 一切正常、留言瀑布照流,只有轉換率悄悄歸零——監控上最難抓的,就是這種「什麼都沒壞,只是沒有產出」的失敗。
修法就是留言章講過的那個「下策」:單筆失敗直接略過、其他照跑——記憶有點久遠,但應該就是這次事故之後改的。放進演化史看,那個被檢討過的「略過」其實是從更慘的「整批死」止血而來:整批死(事故)→ 單筆略過(最快的止血)→ dead-letter 補救(重來版的第三步)。爆炸半徑先縮小,完整性之後再補——順序是對的,只是第三步當年沒走完。
同步側的戰記是一條完整的因果鏈,值得按時間順序講完。
第一環:一支包山包海的 API。 使用者登入首頁,打「檔期列表」API——回應裡是詳細資訊,甚至一路帶到商品資訊。打爆單 process 的從來不是 13,000 人這個數字,是每個請求的重量:查詢深、序列化重、payload 肥,每個登入使用者都來一發「全站詳情」。而它之所以是壓垮駱駝的那支,有個對照:商品資訊頁有 CDN 頂著(圖與靜態內容不經過我們的機器),其餘讀路徑全是 PostgreSQL 硬扛、沒有任何應用層快取——尖峰時 DB 真正的重擔,就集中在這支列表 API 上。
第二環:緊急止血。 立刻把單 process 撤下,中間插上 traefik,開四台 API service——撐住了。但「要做的事太多,撐住就先放著了」——肥端點原封不動地留在那裡。這是技債的標準生命週期:止血和治本之間隔著的不是能力,是永遠排不進的優先序。
第三環:下一次壓力測試,是攻擊者免費做的。 某天 DDoS 直接炸開——一小時內動都動不了。緊急開了 GCP 的 Cloud Armor,沒屁用;帳單倒是一小時多了 1,000 美元,還要另外付 Cloud Armor 的費用。GCP 真的是很會賺。
第四環:雲端菩薩。 最後把 domain name 那層轉到 Cloudflare——就沒事了,還免費。真的是雲端菩薩拯救世人。
這條鏈的教訓,每一環一條:API 的 schema 設計就是容量工程的第一線(最便宜的 scaling 是不要傳沒人要的欄位);「撐住就先放著」的下場是讓攻擊者替你排技債的優先序;以及最少人講明白的一條——你的防禦不能跟攻擊一起計費。per-request 計價的 L7 防禦,在 DDoS 之下等於幫攻擊者放大你的帳單;Cloudflare 在 DNS/邊緣層把流量吸掉,免費層就夠——防禦的正確位置在邊緣,不在 origin 門口。
DDoS 那一小時,系統動不了是一種傷,帳單多一千美元是另一種——而且後者在攻擊結束後還會繼續:你為了防禦加購的服務,也在跟著流量計費。雲端時代的安全設計必須把成本模型算進去:攻擊者的成本趨近零,你的防禦若隨請求計價,這場仗在經濟上就輸了。把攻擊擋在計費表之外(邊緣層、固定費率、免費層),跟把攻擊擋在系統之外一樣重要——安全架構的一半,是財務架構。
明天講打這些仗的人:沒有 SRE 的年代,backend lead 的上線日記。
本文改寫自我的部落格系列《Re:從零開始做直播代購電商平台》,本篇完整版:https://blog.aidan.tw/blog/rezero-flash-crowd/