非同步管線的背壓(backpressure)
站在裝配線的末端,想像第一站把物件甩上輸送帶的速度,是最後一站裝箱速度的兩倍。物件在站台之間堆積;最終從帶上溢出、掉到地上。工廠的解法是一個往回傳的訊號:裝箱站被淹沒時,它叫它前面的站慢下來,那站又叫它前面的,一路傳回起點。背壓正是並行管線裡這個往回傳的「慢下來」訊號:一種讓不堪負荷的消費者去頂住過快生產者的辦法。
具體而言,一個生產者任務把物件餵給一個消費者任務,通常透過一個通道或佇列。沒有背壓時,若生產者較快,在通道裡等待的物件就無界地增長——一個無界佇列默默變成記憶體洩漏,再變成記憶體耗盡崩潰,過程中延遲還一路膨脹。背壓藉著讓通道有界來封住這點:緩衝區滿了時,生產者的發送操作會阻塞或暫停(它去 await),直到消費者取走某物、騰出一個槽。那個 await 就是背壓——生產者真的被迫等待,於是它的速率被節流到與消費者相符。這個機制會沿著一串階段往上傳:一個緩慢的最末階段塞滿它的收件匣,這使它前面的階段停滯,又使再前面的停滯,於是整條管線自我調節到它最慢一環的速度。其他策略也存在(丟最舊的、取樣、或像反應式串流的「需求」那樣明確請求 N 個項目),但有界緩衝的阻塞是常見的預設。
背壓之所以重要,是因為它就是「負載下優雅降級的系統」與「直接倒下的系統」之間的差別。沒有它,一波流量尖峰不只是讓你變慢——它耗盡記憶體並崩潰。有了它,系統保持有界:它變慢、它可能拋棄或緩衝,但它存活。初學者錯過的誠實提醒是:背壓不會把慢的消費者變快。它把資源使用框住、把瓶頸浮現出來,但根本的上限仍是最慢的階段;若資料真的來得比你永遠處理得了的還快,背壓會逼出一個真正的抉擇——阻塞來源、丟棄資料、或橫向擴展——而不是裝沒事、稍後再崩潰。
有界通道施加背壓:tx.send(item).await 在有 32 個項目未被消費時就阻塞,把生產者節流到消費者的速率。無界通道則會任由待辦堆增長,直到記憶體耗盡。
一個滿載的有界緩衝區讓生產者去 await;那個等待就是背壓,把它節流到消費者的速度。
背壓框住資源使用;它不會讓慢的消費者變快。真正的上限是最慢的階段。若輸入真的永遠快過處理,你必須選擇阻塞、丟棄、或橫向擴展——沒有辦法用有界的記憶體吸收一個無界的速率。