為什麼我們不再自己架 WebSocket 即時通訊伺服器
· Hibira
我們自己架的即時伺服器一遇到訊息尖峰就塞住,還得自己追 PHP2Shell 這類漏洞的修補。我們踩過的坑、自己架需要持續投入的工作,以及託管服務替你承擔了什麼。

在做 Hibira 之前,我們在過去的專案裡自己維運過 WebSocket 伺服器,當時直接採用現成的 PHP 開源方案,架成一個獨立服務,單一節點運行。即時通訊只是整個系統裡的一小塊,只要功能正常,平常也沒有人特別注意。直到訊息量變大,問題才開始浮現:伺服器忙著處理手上的訊息,新的請求在後面排隊,延遲一路往上爬。等訊息終於送到使用者手上,內容已經過時了。報價已經變了、比分早就更新,「對方正在輸入」的提示在對方送出訊息之後才出現。
這篇文章整理那次的經驗:即時伺服器為什麼可能在尖峰時塞住、自己架的話要補上哪些東西,以及什麼時候交給託管服務比較划算。回頭看,問題不在於用了開源方案,而是我們當初只確認功能能用,沒有規劃後續的監控、擴充和維護。自己架是可以做得很好的,也有成熟的開源伺服器可以用;要付出的是持續的工程工作,這篇談的就是這些工作。
塞住是怎麼發生的
即時伺服器做的事情看起來很單純:收到一則事件,送給所有訂閱的連線。問題在於這件事的工作量會被放大,而且只要有一段慢下來,後面的全部都得等。
進來的比處理的快,佇列只會變長
這是排隊的基本規則:只要訊息進來的速度超過伺服器處理的速度,佇列就會一直變長,每一則新訊息都得等前面所有的訊息處理完。
用一個簡單的數字算算看。假設伺服器每秒最多能寫出 5,000 則訊息給用戶端(這裡算的是實際送出的份數,不是發布次數,下一節會說明差別),尖峰時每秒有 6,000 則要送,持續 10 秒。這 10 秒會累積 10,000 則處理不完的訊息。假設尖峰結束後新訊息完全停止,伺服器也還要再花 2 秒才能清完,佇列最後面那則訊息會晚 2 秒才送出。如果尖峰後還有正常流量進來,清得更慢。
對網頁來說,慢 2 秒是體驗差一點;對即時功能來說,2 秒前的報價、位置或遊戲狀態,常常就是錯的資料。
一則事件,乘上每一位訂閱者
發布一次不等於送出一次。一個有 2,000 人訂閱的頻道,每發布一則事件,伺服器就要寫出 2,000 次。所以實際的工作量是「每秒發布次數 × 訂閱人數」:每秒發布 6,000 則、每則送給 2,000 人,最多就是每秒 1,200 萬次寫出。直播聊天室、比賽比分、營運儀表板這類一對多的情境,最容易在人數變多的那一刻突然超過負荷。
等待會互相傳染
伺服器一忙,負責發布事件的 HTTP 請求也開始變慢。你的後端在等它回應,自己的請求也跟著堆積;請求逾時之後如果自動重試,又在已經過載的伺服器上多加一份工作。原本只是即時伺服器慢,最後變成整個系統都慢。
擴充是最難的一步
我們當時的 WebSocket 伺服器是獨立部署的,和應用程式分開,不跟主程式共用同一台機器。真正卡住的是擴充:一台伺服器的處理量有上限,想加第二台,所有機器就得共享頻道訂閱和線上狀態,事件才送得到連在另一台上的使用者,前面還要有負載平衡器分配連線。我們那台只有一個節點,這些機制都沒有,所以根本沒辦法擴容,尖峰時只能讓它一台硬撐。而且因為只有一個節點,每次更新或重新啟動,所有 WebSocket 連線都會同時斷開,再一起重連。
看不見,只能等出事才知道
還有一個問題是看不見。我們當時是自己架設、自己維運,沒有視覺化的監控圖表,也沒有告警,連線數、訊息處理量和伺服器負載都無法即時掌握,更沒有盯著佇列長度、訊息延遲和被拖慢的連線,伺服器的實際狀況其實一直掌握不清楚。CPU 看起來還好,不代表訊息準時送到。結果往往要等到使用者受到影響、主程式也被拖慢,我們才發現:發布事件的請求卡住,主程式等著它回應,跟著一起變慢,就像上面「等待會互相傳染」說的那樣。
另一個原因:執行環境的安全更新
效能之外,讓我們決定不再自己維運的另一個原因是安全。我們當時也遇過 PHP 的安全漏洞,只能自己追著修補。自己架伺服器,作業系統、語言執行環境、函式庫的漏洞都得自己盯,一個都不能漏。
最近的一個例子是 PHP 官方在 2026 年 10 月修補的兩個嚴重漏洞,被稱為「PHP2Shell」(加拿大網路安全中心通報 AL26-026):
- CVE-2026-109618:處理記憶體不足(OOM)時發生的記憶體損壞。
- CVE-2026-109619:請求之間殘留的狀態造成的 use-after-free。
兩者的 CVSS v4.0 分數都是 9.2,可能讓未經驗證的攻擊者遠端執行程式碼。受影響的是處理網頁請求的 PHP,例如 PHP-FPM、Apache mod_php 和 php-cgi。修補版本是 8.2.35、8.3.36、8.4.27 和 8.5.12,8.1 以前的版本已經沒有官方的例行更新。
要說清楚的是,這兩個漏洞出在處理網頁請求的 PHP 上,常駐的 WebSocket 程序不一定會受影響。我們舉它當例子,是為了說明例行的維護工作,而不是 WebSocket 本身的缺陷:不管伺服器用什麼語言寫,你用到的軟體一有漏洞公開,就得自己確認是否受影響、排時間升級、重新部署,而且越快越好。
自己架的話,要補上這些
這些問題都有解法,只是每一項都是持續的工作,不是設定一次就好:
- 和應用程式分開部署。 讓即時伺服器可以獨立擴充、獨立部署,尖峰時才不會拖垮其他功能。
- 每條連線各自的有上限佇列。 寫出不能互相等待。每條連線有自己的佇列,滿了就代表這個用戶端跟不上,應該中斷它,而不是讓它拖慢別人。
- 把收訊息和送訊息分開。 發布用的 API 不應該等所有訂閱者都送到才回應,否則廣播一慢,你的後端也跟著慢。
- 水平擴充。 單台伺服器總有上限。要加機器,就需要讓所有機器共享頻道和線上狀態,並在前面放負載平衡器。
- 規劃重連與漏掉的訊息。 斷線、部署、網路切換之後,用戶端都會重新連線。重連要有退避,才不會全部同時湧回來;斷線期間發布的事件不會補送,所以應用程式重新連上後要再取得一次最新狀態。
- 以尖峰來測容量。 用平均流量做的壓力測試會告訴你「沒問題」。要測的是尖峰的好幾倍,以及大量用戶端同時重新連線的情況。
- 監控佇列與延遲,而不只是 CPU。 CPU 看起來還有空間時,佇列可能已經在變長。要能看到訊息排了多久、有多少連線因為太慢被中斷,並在變差時收到告警。
- 有人值班。 尖峰通常就是你最重要的時刻:活動開賣、比賽最後一分鐘、促銷開始。伺服器出事時要有人能處理。
- 追安全更新。 作業系統、語言執行環境和函式庫的漏洞一公開,就要評估影響、升級並重新部署。
依序寫出
每條連線各自的佇列
託管服務替你承擔了什麼
使用託管的即時服務,上面這些工作就由服務商負責。以 Hibira 為例:
- 慢的用戶端不會拖慢別人。 每條連線有自己的有上限佇列,寫入不會互相等待。佇列滿了或寫出逾時的用戶端會以 Pusher 協定的 4100 錯誤碼中斷,pusher-js 收到後會稍等一下再重新連線。中斷是為了保護其他連線,並不會讓那台裝置的網路變快;如果它還是很慢,重連後可能再次被中斷。
- 重新連上後由你的應用程式恢復狀態。 Pusher 協定不會補送用戶端斷線期間漏掉的事件。付費方案可以在應用程式設定中開啟頻道歷史,重新連線的用戶端就能用
pusher:rewind補收最近幾分鐘的事件。不過報價、遊戲狀態這類一定要正確的資料,請把事件當成更新通知,連線恢復後向你的後端重新取得最新狀態;pusher-js 和 Laravel Echo 都會通知你連線已恢復。 - 你看得到發生了什麼。 這類中斷會出現在應用程式的錯誤卡片上,標示為「過慢的用戶端被中斷」,顯示過去 24 小時,所有方案(包括免費方案)都有,你不用自己去翻伺服器記錄。
- 容量由我們規劃。 伺服器組成叢集,共享頻道與線上狀態,前面有負載平衡器,並依 CPU、記憶體和最忙那台伺服器的連線數自動加開伺服器。新伺服器需要幾分鐘才能開始接連線,所以自動擴容應付的是持續成長的流量,不是瞬間的尖峰;在那之前,滿載的伺服器會用 4100 婉拒新連線,讓用戶端重試到其他伺服器。已經建立的連線不會被搬移。這是我們這一側的基礎架構;你的用量仍以方案的硬上限為準,不會在帳單上給你意外,而可用性 SLA 包含在 Enterprise 方案。
- 使用的是 Pusher 協定。 你現有的 pusher-js、Laravel Echo 和伺服器端函式庫都能直接使用,換服務通常不需要重寫業務邏輯:改主機位址和憑證、重新設定 Webhook,再確認你用到的功能和限制。遷移指南列出支援範圍,以及怎麼不停機切換。
- 修補由我們負責。 WebSocket 伺服器和它底下執行環境的安全更新由我們處理。你仍然要更新自己的應用程式,但要盯的東西少了一項。
代價也要說清楚:超過免費額度後要付月費,事件會經過第三方,而且要接受方案的上限。
什麼時候自己架仍然合理
託管服務不是唯一的答案。以下情況,自己架可能更適合:
- 流量不大、穩定,而且沒有明顯的尖峰。
- 事件內容不能離開你的基礎架構。
- 你已經有維運團隊、監控和值班制度,多一個常駐服務不是負擔。
比較成本時要算整體:自己架的成本是伺服器,加上監控、擴容、修補和值班花掉的工程時間;託管服務則是依用量付的月費。流量小又穩定、已經有人在顧的話,自己架往往比較省;需要隨尖峰擴充、又沒有專人維運時,人力時間常常比伺服器還貴。
用 Laravel 的話,自己架 Reverb 還是用託管服務?這個比較我們下次再揭露。因為兩邊都使用 Pusher 協定,之後改變主意也只是改設定。
我們的結論
市面上已經有成熟的託管服務,直接採用可能更省事。但我們還是決定自己重建一遍:一方面想真正弄懂 WebSocket 和分散式即時通訊背後的運作,另一方面想實際量測不同架構的效能、資源消耗和營運成本,也藉此理解這類服務是怎麼定價的。
我們把即時通訊重新做成可以水平擴充的服務。前面放負載平衡器,每條連線有自己的有上限佇列,負載增加時自動加開伺服器,讓流量分散到更多伺服器上,而不是讓一台伺服器硬撐;連線數、訊息量和延遲都有監控和告警。我們先在內部使用,之後也開放給外部,這就是 Hibira。
自己架的 WebSocket 伺服器可以跑得很好。難的不是讓它跑起來,而是背壓控制、容量規劃、故障隔離、監控和安全更新這些持續的工作,讓它在最忙的那幾分鐘依然準時。那正好是使用者最在意、也最容易出事的時候。Hibira 把這些持續的工作做成託管服務,讓你的團隊把時間花在產品上。
Hibira 的免費方案不需要信用卡,足夠開發和小型應用使用。已經在用 Pusher 的話,通常只要更新連線設定,不需要重寫業務邏輯;步驟在遷移指南,我們自己的遷移經驗下次再揭露,敬請期待。