🎯 什麼情境該想到我
當你「想用一條與業務資料分開的管理通道,對散布各處的訊息元件下設定、監控、下指令」的時候。
⚙️ 怎麼用(步驟 / 公式)
意圖:用一條獨立於主要訊息流的控制通道,連到系統中各元件,用來傳送管理指令與收集狀態,集中管理整個訊息系統。
做法 / 結構要點:
- 每個元件除了處理業務訊息,也連上控制匯流排。
- 透過控制通道下達設定變更、啟停、心跳查詢等管理指令。
- 元件也可經由控制匯流排回報統計、狀態與異常。
★★ 核心紀律:管理流量與資料流量必須分開
★ 為什麼不能共用同一條通道:
① ★★ 資料通道積壓時,你最需要下指令(暫停消費、清空佇列)
→ 而指令排在 500 萬則訊息後面 ⇒ ★ 永遠到不了
② ★ 管理指令需要不同的權限(誰能叫服務停止消費?)
③ ★ 管理指令通常需要「立即」而非「可靠」(過期的指令不該執行)
★★ 第 ① 點是這個模式存在的全部理由:控制平面與資料平面分離,
這正是 Kubernetes、service mesh、資料庫管理介面背後的同一個原則。
★ 今天的控制匯流排長什麼樣(多數不是訊息)
| 用途 | 2003 年的做法 | ★ 今天 |
|---|---|---|
| 健康檢查 | 心跳訊息 | ★ /healthz、/readyz HTTP 端點;k8s probe |
| 指標 | 統計訊息 | ★ Prometheus /metrics 拉取、OTel |
| 動態設定 | 設定訊息 | ★ ConfigMap/Consul/LaunchDarkly(feature flag) |
| 指令 | 命令訊息 | ★ /admin/* 端點(★ 需鑑權)、kubectl exec、專用 admin API |
| 日誌層級調整 | 設定訊息 | ★ /debug/vars、net/http/pprof、動態 log level 端點 |
| 服務發現 | 註冊訊息 | ★ k8s Service/Consul |
★★ 結論:這個模式今天大多由「基礎設施」而非「訊息」實現——
而它的核心洞見(控制平面分離)完全沒有過時。
★ Go 的實作:一個獨立的 admin server
// ★★ 管理端點要跑在「另一個 port」上,不要和業務流量共用
func serveAdmin(ctx context.Context, svc *Service) error {
mux := http.NewServeMux()
mux.Handle("/metrics", promhttp.Handler())
mux.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK) // ★★ liveness 不要碰外部依賴
})
mux.HandleFunc("/readyz", func(w http.ResponseWriter, r *http.Request) {
if err := svc.Ready(r.Context()); err != nil { // ★ readiness 可以檢查依賴
http.Error(w, err.Error(), http.StatusServiceUnavailable); return
}
})
mux.HandleFunc("/admin/pause", requireAuth(svc.PauseConsumers)) // ★ 一定要鑑權
mux.Handle("/debug/pprof/", http.DefaultServeMux) // ★ 只在內網
return (&http.Server{Addr: ":9090", Handler: mux}).ListenAndServe()
}★★ 兩個不可妥協的點:
- 獨立的 port —— 業務 port 被打爆時,管理端點還活著。
★ 也讓防火牆能只對內網開放 9090。 - ★ liveness 檢查不要碰外部依賴 —— 資料庫一抖,
所有 pod 的 liveness 失敗 → 全部被重啟 → 雪崩。
→ 依賴檢查放 readiness(只會停止導流,不會重啟)。
★★ 「暫停消費」是最有價值的一個控制指令
// ★ 一個能在事故中救命的能力:不重新部署就能停止消費
type Service struct{ paused atomic.Bool }
func (s *Service) consumeLoop(ctx context.Context) error {
for {
if s.paused.Load() {
select {
case <-ctx.Done(): return nil
case <-time.After(time.Second): continue // ★ 暫停時不要 busy loop
}
}
…
}
}★ 為什麼值得做:下游資料庫掛了、或發現訊息會造成錯誤資料時,
你需要立刻停止消費而不是重新部署(重新部署要幾分鐘,而且 pod 一起來又開始消費)。
★ 配套的還有「調整消費速率」 —— 追趕積壓時避免打爆下游(見 持久訂閱者)。
★ 安全:管理端點是攻擊面
★ 三個必做的事:
① ★★ 鑑權 —— /admin/pause 沒有鑑權 = 任何人可以讓你的服務停止工作
② ★ 只綁內網介面或用 NetworkPolicy 限制
③ ★★ pprof 端點洩漏大量內部資訊(堆疊、記憶體內容、goroutine 狀態)
→ ★ 絕對不要暴露在公網
⚠️ Go 的一個具體陷阱:import _ "net/http/pprof" 會把 handler
註冊到 http.DefaultServeMux。如果你的業務 server 用了 DefaultServeMux,
pprof 就自動暴露在業務 port 上了——這是真實發生過的洩漏。
★ 兄弟辨析
🧪 我實際套用的紀錄
- (待填)
⚠️ 注意 / 什麼時候不適用
-
控制通道要與業務通道分離,避免互相干擾。
-
是許多系統管理模式(繞道、竊聽、測試訊息等)的協調基礎。
-
管理指令的安全與權限要控管。
-
★★ 管理流量與資料流量必須分開。資料通道積壓時你最需要下指令,而指令排在 500 萬則後面永遠到不了。
-
★★ 管理端點跑在獨立的 port 上。業務 port 被打爆時它還活著,也方便只對內網開放。
-
★★ liveness 檢查不要碰外部依賴。資料庫一抖 → 所有 pod 被重啟 → 雪崩。依賴檢查放 readiness。
-
★★
/admin/*一定要鑑權。沒鑑權 = 任何人能讓你的服務停止工作。 -
★★
import _ "net/http/pprof"會註冊到http.DefaultServeMux。業務 server 若用了它,pprof 就暴露在業務 port 上了。 -
★ 「暫停消費」是事故中最有價值的能力。重新部署要幾分鐘,而且 pod 起來又開始消費。
-
暫停時不要 busy loop,用
time.After或 channel 等待。 -
今天這個模式大多由基礎設施實現(Prometheus、k8s probe、feature flag),不是訊息——但控制平面分離的洞見沒有過時。