Bulkhead 隔離
Bulkhead 的概念來自船舶的隔艙設計 — 船體分成多個獨立的水密隔艙,一個隔艙進水不會讓整艘船沉沒。服務設計中,bulkhead 把不同的工作負載隔離到各自的資源池,一個工作負載的過載或故障不會消耗其他工作負載的資源。
隔離什麼
服務中的共享資源是 bulkhead 的隔離對象:
| 共享資源 | 不隔離時的風險 | 隔離方式 |
|---|---|---|
| Goroutine/Thread | 一個慢查詢佔住所有 goroutine,整個服務不回應 | 每類工作分配獨立的 goroutine pool |
| 資料庫連線 | 一個大查詢佔住 connection pool,其他查詢排隊 | 不同工作類型用不同的連線池 |
| 記憶體 | 一個功能的 buffer 無限增長,OOM 殺掉整個 process | 每個功能的 buffer 有獨立上限 |
| CPU | 一個計算密集任務佔滿 CPU,其他請求延遲 | cgroup 或 GOMAXPROCS 限制 |
實作模式
獨立 Goroutine Pool
Go 中用有限容量的 channel 模擬 goroutine pool:
1var (
2 ingestPool = make(chan struct{}, 100) // ingestion 最多 100 goroutine
3 queryPool = make(chan struct{}, 20) // query 最多 20 goroutine
4 rulePool = make(chan struct{}, 10) // rule engine 最多 10 goroutine
5)
6
7func handleIngest(w http.ResponseWriter, r *http.Request) {
8 select {
9 case ingestPool <- struct{}{}:
10 defer func() { <-ingestPool }()
11 processIngest(r)
12 default:
13 http.Error(w, "ingestion overloaded", http.StatusServiceUnavailable)
14 }
15}Ingestion 的 100 個 goroutine 全部被佔用時,新的 ingestion 請求被拒絕(503),但 query 和 rule engine 的 goroutine 不受影響。
獨立 Connection Pool
資料庫連線池按工作類型分開:
1// driver name 依實際使用的 driver 而定(modernc.org/sqlite 用 "sqlite"、mattn/go-sqlite3 用 "sqlite3")
2ingestDB := sql.Open("sqlite", "events.db")
3ingestDB.SetMaxOpenConns(10) // ingestion 專用
4
5queryDB := sql.Open("sqlite", "events.db")
6queryDB.SetMaxOpenConns(5) // query 專用SQLite 的特殊情況:SQLite 是檔案級鎖定,多個連線池打開同一個檔案時仍共享 write lock。連線池隔離在 SQLite 上主要隔離的是 Go 層的 goroutine 等待,不是 DB 層的鎖定。PostgreSQL 的連線池隔離則是真正的資源隔離。
容量分配
Bulkhead 的每個隔艙分配多少資源是設計決策。分配依據是「這個工作負載的優先順序和預期併發量」:
| 工作負載 | 優先順序 | 預期併發 | 分配 |
|---|---|---|---|
| Ingestion | 高(不能丟事件) | 高(多 SDK 同時 flush) | 60% |
| Query | 中(dashboard 查詢) | 低(dashboard 定期刷新) | 25% |
| Rule engine | 低(觸發可延遲) | 低(規則命中是少數事件) | 15% |
分配比例不需要精確 — 重點是每個隔艙有獨立的上限,而非共享一個無差別的總上限。
監控系統的應用
Collector 同時承載 ingestion(接收事件)、query(dashboard 查詢)和 rule engine(規則評估)三種工作。不隔離時,一個複雜的 dashboard 查詢(full table scan)可能佔住所有資料庫連線,讓 ingestion 的寫入也排隊等待。
Bulkhead 設計讓 ingestion 和 query 各自的過載互不影響:
- Ingestion 的 goroutine pool 滿了 → SDK 收到 429 → 離線 buffer 接手
- Query 的 goroutine pool 滿了 → dashboard 暫時顯示 loading → 不影響 ingestion
- Rule engine 的 goroutine pool 滿了 → 規則評估延遲 → 不影響事件接收和查詢
下一步路由
#devops #traffic-management #bulkhead #isolation #resource-pool