上一篇講到「開個背景 thread 不就好了」會在三個地方翻車。這篇把三個翻車點一個個拆開——因為它們剛好對應 Queue 真正在賣的三個保證,搞懂了你才知道自己付錢(多養一套服務)買到了什麼。

保證一:ack——「做完才算完」的簽收機制

先想像沒有它的世界:thread 寄信寄到一半掛了,這封信就消失了,而且系統不知道它消失了

Queue 的解法是把「拿到任務」跟「完成任務」拆成兩件事:

worker 從 queue 領走任務 ──→ 任務標記「處理中」(不是刪掉!)
worker 做完,回報 ack     ──→ queue 這才真的把任務刪掉
worker 掛了,一直沒 ack   ──→ 超過時限,任務自動回到隊伍,換人做

ack(acknowledge)就是簽收單:貨送到要簽名,沒簽名物流就當作沒送到、會再送一次。這一來一回,換到的保證叫「at-least-once」——每個任務至少被做一次,掛掉就重做。

代價也在這句話裡:「至少一次」意思是可能不只一次。worker 做完了、還沒來得及 ack 就斷線——queue 以為沒做過,再派給下一個 worker,同一封信就寄了兩次。所以用 queue 的鐵律:你的任務要能被重複執行而不出錯(這個性質叫冪等,之後的文章會專門講怎麼做到)。

保證二:worker——工作者跟你的網站是兩群人

上一篇說「thread 跨不出這台機器」,展開講:

thread 活在你 web 程式的 process 裡。web 重新部署,thread 陪葬;流量大到要多開幾台 web 機器,各台的 thread 各做各的,沒人統籌。

Queue 把「接請求的」跟「做粗活的」拆成兩群獨立的程式:

  • Web 那群只做一件事:把任務丟進 queue,馬上回應使用者。
  • Worker 那群是獨立的 process(php artisan queue:work 開的就是它),可以跑在同一台機器、也可以跑在完全不同的十台機器上——反正大家連的是同一個 queue,誰有空誰領任務。

這帶來兩個實際的好處:擴充是各自獨立的(寄信塞車就多開 worker,不用動 web);部署也是各自獨立的(改了寄信邏輯只要重啟 worker,網站不中斷)。

代價:你從「一個程式」變成「兩群程式+一個中間人」,部署、監控、log 都變成多套。這就是為什麼上一篇說「先有痛,才上 queue」。

保證三:持久化——「掛了還在」是有條件的

上一篇說「紙條乖乖躺在 queue 裡,掛了還能重來」,也提醒過這有前提。這裡把前提講白。

「掛掉」其實分兩種,保護方式完全不同:

誰掛了靠什麼保護預設有嗎
worker 掛了ack 機制(保證一)——沒簽收就重派✅ 幾乎所有 queue 工具內建
queue 本身掛了(broker 重啟、機器斷電)持久化——紙條有沒有寫到硬碟⚠️ 看設定,不一定

持久化(persistence)就是「除了放在記憶體,還要落到硬碟」。沒開的話,broker 一重啟,記憶體裡排隊中的任務全部蒸發。而很多工具預設沒有全開——因為寫硬碟比寫記憶體慢,工具讓你自己選「快」還是「穩」。

所以「用了 queue 就不會掉任務」是句只對一半的話。完整版是:worker 掛不掉任務(ack 保證),broker 掛不掉任務的前提是你有開持久化。用 Laravel 的 database driver 天生持久(任務就是 DB 裡的一筆 row);用 Redis 或 RabbitMQ,上 production 前去檢查那幾個設定(Redis 的 AOF、RabbitMQ 的 durable queue + persistent message——名詞先記著,選型篇會再遇到)。

三個保證,一句話收

Queue 賣你的是:做完才算完(ack)、誰有空誰做(worker)、掛了還在(持久化,記得開)。thread 三個都給不了——這就是那個「自己開 thread」的直覺,跟一套正經 queue 系統之間的距離。

而三個保證各有標價:ack 換來重複執行(你要冪等)、worker 換來多套部署(你要監控)、持久化換來寫硬碟的延遲(你要取捨)。沒有白拿的保證,只有你願不願意付的代價。

接下來往哪走