🎯 什麼情境該想到我
當你「一份資料太大、或是連續的一大串,得拆成多則有序訊息來傳」的時候。
⚙️ 怎麼用(步驟 / 公式)
意圖:用一組帶有序資訊的訊息,傳遞任意大量或連續的資料。
做法要點:每則訊息標頭帶三項欄位:
- 序列識別碼:同一序列的訊息共用,用來歸屬。
- 位置編號:這是序列中的第幾則。
- 序列結束標記:標示是否為最後一則。
接收端據此判斷是否收齊、並依序重組成完整資料。
★★ 先考慮不要用它:讓處理與順序無關,比重建順序便宜得多
// ★★ 最實用的做法:訊息帶版本號,消費端丟棄過舊的
type OrderUpdated struct {
OrderID string `json:"order_id"`
Version int64 `json:"version"` // ★ 來源的單調遞增版本
Status string `json:"status"`
}
// 消費端:條件更新(樂觀鎖)
res, err := tx.Exec(ctx, `
UPDATE order_read_model SET status = $1, version = $2
WHERE order_id = $3 AND version < $2`, // ★★ 舊訊息不會蓋掉新狀態
e.Status, e.Version, e.OrderID)
// ★ 影響 0 列 = 這則訊息過舊,直接 ack 掉★★ 這一招同時解掉三個問題:亂序、重複、以及「可以完全並行處理」。
而 訊息序列/重排序器 只解掉亂序,代價是有狀態元件與逾時管理。
★ 所以順序的第一個問題永遠是:真的需要「按順序處理」,還是只需要「最終狀態正確」?
多數情況是後者(見 工具-交易與隔離等級 的樂觀鎖)。
★ 真的需要序列時:三個必備欄位
type SequencedMessage struct {
SequenceID string `json:"sequence_id"` // ★ 這一組的識別(= correlation id)
Position int `json:"position"` // ★ 第幾則,從 1 開始
Total int `json:"total"` // ★★ 共幾則;-1 表示「未知,等 IsLast」
IsLast bool `json:"is_last"` // ★ 串流式(總數事先未知)時必備
}★ Total 與 IsLast 要至少有一個,否則接收端不知道什麼時候收齊了
(這正是 聚合器 的「完成條件」問題)。
⚠️ Total 事先未知的情況很常見(串流、分頁、邊產生邊送)→ 那就必須有 IsLast。
★ Broker 的原生順序保證(各家差很多)
| broker | 順序保證範圍 |
|---|---|
| Kafka | ★ 單一分區內嚴格有序。跨分區完全無保證 → 用同一個 key 進同一分區 |
| RabbitMQ | ★ 單一 queue + 單一 consumer 才有序。多 consumer 就沒了 |
| SQS 標準 | ★★ 完全不保證順序 |
| SQS FIFO | ★ 同一個 MessageGroupId 內有序(有吞吐上限) |
| NATS JetStream | stream 內有序 |
★ max.in.flight.requests.per.connection 的陷阱(Kafka):
預設是 5。未開啟冪等時,重試會讓後面的批次先成功 ⇒ 順序亂掉。
✅ 但 enable.idempotence 預設為 true(Kafka 4.0,2026-09-25 查證),
在 ≤5 的情況下 broker 會依 sequence number 重排 ⇒ 順序仍有保證。
⚠️ 手動把 enable.idempotence 關掉 + in-flight > 1 = 靜默的亂序。
★★ 序列最難的部分:缺漏怎麼辦
★ 收到 1、2、4 —— 3 在哪?
① 網路慢,稍後會到 → 等(★ 但要等多久?)
② 發生者根本沒發 → 永遠等不到
③ 進了 DLQ → ★ 永遠等不到,而且你不知道
④ 被判定無效丟棄 → 同上
必須做的三件事:
① ★ 逾時:等不到就放棄,並且「明確記錄缺了哪幾則」(不要靜默不完成)
② ★ 記憶體上限:等待中的部分序列要有數量與大小上限,否則一個壞的生產者能 OOM 你
③ ★★ 持久化:等待狀態不能只在記憶體。重啟後必須能繼續等(或明確放棄)
★ 這三件事讓「有狀態的序列處理」變成一個要維運的元件——
這正是為什麼「讓處理與順序無關」那條路更值得先試。
★ 兄弟辨析
| 差別 | |
|---|---|
| 分割器 | ★ 產生序列的那一端(一則拆成 N 則,加上 position/total) |
| 聚合器 | ★ 把序列合回一則(需要完成條件) |
| 重排序器 | ★ 只排序不合併——輸出仍是 N 則,但按順序 |
| 關聯識別碼 | SequenceID 通常就是它 |
| 點對點通道 | 順序 vs 並行的根本取捨在那裡 |
🧪 我實際套用的紀錄
- (待填)
⚠️ 注意 / 什麼時候不適用
-
接收端需能緩存並重組,還要處理缺漏。
-
若訊息可能亂序到達,搭配重排序器先排好。
-
與分割器產生多則訊息的概念相關,但這裡強調「同一份資料的有序切片」。
-
★★ 先考慮「讓處理與順序無關」:帶版本號 + 條件更新(
WHERE version < $new)同時解掉亂序、重複、並行三個問題。 -
★
Total與IsLast至少要有一個,否則接收端不知道何時收齊。串流情境必須有IsLast。 -
★★ Kafka 的順序保證只在單一分區內。SQS 標準佇列完全不保證順序。
-
★ 手動關掉
enable.idempotence且 in-flight > 1 = 靜默亂序(預設是開啟的,2026-09-25 查證)。 -
★★ 缺漏的那一則可能永遠不來(進了 DLQ 或被判無效)。必須有逾時,而且要明確記錄缺了哪幾則。
-
★ 等待中的部分序列要有數量與大小上限,否則一個壞生產者能讓你 OOM。
-
★ 等待狀態必須持久化,否則重啟後所有半成品序列都變成永遠等不到。
-
有狀態的序列處理是要維運的元件(逾時、記憶體、狀態恢復)——這是它真正的成本。
🔗 相關工具
- 重排序器 Resequencer(把亂序的序列重排回正確順序)
- 分割器 Splitter(也是把一則拆成多則,但按項目而非切片)
- 關聯識別碼 Correlation Identifier(序列識別碼即一種關聯機制)
- 企業整合模式
- 分割器 —— 產生序列的那一端
- 聚合器 —— 把序列合回一則
- 重排序器 —— 只排序不合併
- 關聯識別碼 —— SequenceID 的來源
- 點對點通道 —— 順序 vs 並行的取捨
- 工具-交易與隔離等級 —— 樂觀鎖式的條件更新
- 工具-資料分區 —— 分區鍵決定順序粒度