Engineering · 1 min read
用 TryAcquire 做 load shedding:快速拒絕勝過緩慢排隊
Acquire 排隊只是把 OOM 搬到等待區。TryAcquire + HTTP 429 才是真正的 load shedding。佔住資源本身就是問題時,快速拒絕勝過排隊。
Acquire 排隊只是把 OOM 搬到等待區。TryAcquire + 429 才是真正的 load shedding。
Acquire vs TryAcquire
- Acquire(blocking):超限的 caller 排進 FIFO queue。每個 waiter 佔一個 goroutine + 一條連線 + 記憶體,無上限的 queue 只是把 OOM 搬到了等待區。
- TryAcquire(non-blocking):立刻
false→ HTTP 429。這是 load shedding,快速拒絕,讓 caller 重試到別台(keep-alive 讓重試卡在同一台)。 - 選 shedding:當佔住資源本身就是問題(如瀏覽器分頁)。選 bounded queue:當短暫突增可以吸收。
semaphore.Weighted 內部
想像一個停車場,6 個車位(size: 6),目前停了 4 台(cur: 4):
TryAcquire(不等):看一眼,有空位就停進去,沒有就掉頭走。永遠不排隊。
Acquire(願意等):沒空位就排隊等,等到有人開走才進去。排太久可以透過 context 放棄。
Release:開走,空出位子。有人在排隊的話,按順序讓下一台進來。
TryAcquire(1): Acquire(ctx, 1):
lock lock
剩餘 >= 1 且沒人排隊? 剩餘 < 1?
→ cur++, return true → 排進 waiters, 等 channel
(永遠不 block) (block 直到有人 Release)
Release(1):
cur-- → 按排隊順序喚醒下一個 waiter
- 這裡
n=1(一次拿一個位子),size=6→ 同時最多 6 個截圖。 - TryAcquire 回
false時,handler 直接回 429——不排隊、不佔 goroutine、不佔記憶體。 waiters.Len()==0這個條件是為了公平:有人在排隊時,新來的不能插隊。
在這個 OOM 裡的角色
截圖服務的 /capture 用 slots.TryAcquire(1) 限制同時截圖數 = 6。第 7 個以上的請求立刻拿到 429,不會再開瀏覽器分頁,瀏覽器 sidecar 的記憶體就被 bound 住了(共享資源的影響範圍)。
快速的 429 比緩慢的 500 便宜。shed 掉的請求還有機會;排隊排到 OOM 的請求一個都沒救。
參考來源:
- https://github.com/golang/sync/blob/master/semaphore/semaphore.go
- https://pkg.go.dev/golang.org/x/sync/semaphore
- https://kupczynski.info/posts/tcp-proxy-concurrency-limits/
- https://jeffbailey.us/blog/2025/12/16/what-is-load-shedding/
Related: 參見共享資源的影響範圍、keep-alive 讓重試卡在同一台、GOMAXPROCS 限制平行不限並發,或回到系列總覽。