Skip to content
All writing
Engineering · 1 min read

從 19 個 goroutine 到 exit 137:Go 並發 OOM 全紀錄

19 個 goroutine、一個共用瀏覽器、exit 137。從 Go 並發、GMP 排程、記憶體三層模型到 semaphore 修法的全紀錄。

19 個 goroutine、一個共用瀏覽器、exit 137。當 Go runtime 看不見 process 外面的上限,你得自己加上限。

故事

我們的截圖服務出了 exit 137。這個服務透過瀏覽器 sidecar 製作網頁截圖,之前每個請求有自己的瀏覽器實例,後來為了省資源改成共用一個。

當時我對 goroutine 底層運作不太熟。我知道 GOMAXPROCS=1,以為這代表「一次只跑一個」。結果不是。19 個 goroutine 同時開了 19 個瀏覽器分頁,瀏覽器記憶體爆掉,exit 137。所有正在截圖的請求全部失敗。

為了搞懂為什麼 GOMAXPROCS=1 擋不住 19 個並發,我從 process、thread 這些基礎開始往下挖。這系列有這麼多名詞解釋,每一個都是我在追根究底的過程中補上的。

路線圖

Foundations (0)    Mechanism (1)     Memory (1.5)        Trap (2)         Allocator (2.5)    Fix (3)        Pitfalls (4)
process, thread    GMP scheduler     cgroup accounting   shared browser   latency/size-cls   semaphore      wrong target
kernel, syscall    netpoller/epoll   cAdvisor metrics    → correlated OOM fragmentation      TryAcquire→429 GOMEMLIMIT ≠
socket, container  goroutine-per-req allocator concept   backpressure gap return policy      keep-alive off   browser mem
stack, heap, cgo   GOMAXPROCS ≠ cap  heap metrics        fan-in on shared                    shed, not queue git archeology

按數字順序讀,每篇是我在過程中搞懂的一個觀念:

為什麼爆?三個條件湊在一起

net/http (goroutine-per-request)      每個請求自動開 goroutine
        × GOMAXPROCS 只限平行          I/O-parked 的不受限
        × 共用瀏覽器無並發上限         19 個分頁同時存在
        = 瀏覽器 OOM (exit 137)        所有請求全部一起掛
  1. 隱含的並發net/http 為每個請求開 goroutine,handler 裡再開一個,你沒寫 go,並發就已經存在(GOMAXPROCS 與並發)。
  2. GOMAXPROCS 是假天花板:它限制同時「執行 Go 程式碼」的 goroutine 數量,不限制同時「等在 I/O 上」的。Parked goroutine 佔 0 CPU、0 P、0 M(GMP 排程器)。
  3. 共用 = 單一影響範圍:一個瀏覽器死了,所有正在用它的請求全死。Go runtime 看不見 process 外面的記憶體上限,backpressure不會自動回傳(影響範圍)。

怎麼修

  • 在截圖服務加上 semaphore(TryAcquire:超過 6 個同時截圖的請求,直接回 HTTP 429,不排隊(TryAcquire load shedding)。
  • 呼叫端服務收到 429 時,關掉 keep-alive 重試,否則 L4 負載均衡會讓重試卡在同一個 pod(keep-alive 讓重試卡在同一台)。

錯誤示範

有一次嘗試用 GOMEMLIMIT 修 OOM,但 GOMEMLIMIT 控制的是 Go heap,不是瀏覽器 sidecar 的記憶體,fix-locus(修的地方)≠ failure-locus(壞的地方)。git log 回溯才發現,是一步步累積的架構變更才讓 OOM 有機會發生(錯誤的修法)。

集中化省了資源,也集中了風險。加回上限,才不會 19 個請求陪一個瀏覽器一起死。

參考來源:

Tags #go #concurrency #reliability
// connect

Be brave | Be wise | Be grateful

21 BreakinCode

// elsewhere
LinkedInMedium (lang: en)Youtube
wh:~$William Hung· © 2026 Taipei · GMT+8 · Available for collaboration