← 部落格

在亞洲做即時功能:WebSocket 伺服器放在哪裡很重要

· Hibira

為什麼距離決定了即時通訊延遲的下限、從香港到亞洲各地的下限是多少,以及如何量測你自己使用者的往返時間。

聊天訊息、報價跳動或「對方正在輸入」的提示,只要在大約十分之一秒內送到,感覺就是即時的。這段時間大部分花在網路上,而網路時間大多由你選擇的一件事決定:WebSocket 伺服器放在哪裡。

距離決定下限

光在光纖中每毫秒大約前進 200 公里,約是真空中光速的三分之二。所以到 1,000 公里外伺服器的一趟往返,至少要 10 毫秒,這還沒算路由、排隊和處理的時間。實際的海纜不會沿著大圓走,路由器也會增加延遲,所以實測的往返時間常常是這個下限的兩到四倍:我們從香港到台北實測約 30 ms,而下限是 8 ms。

以下是從 Hibira 目前所在的香港(AWS ap-east-1)到亞洲各城市的下限:

這些是物理限制,不是效能測試:沒有任何網路能比這更快,你的實際數字只會更慢。最後一列是用來對照的。很多美國服務把即時基礎架構放在美東,Pusher 的預設 cluster mt1 也在那裡。Pusher 確實有亞洲的 cluster,如果你在用 Pusher,請檢查你的應用程式建立在哪個 cluster。

台北8 ms馬尼拉11 ms曼谷17 ms首爾21 ms新加坡26 ms東京29 ms雅加達33 ms阿什本(美東)131 ms
從香港出發的最短來回時間,由光在光纖中的速度決定

為什麼即時功能比網頁更在意延遲

網頁在載入時只需要往返幾次。即時功能則是每次互動都要付出往返時間:

  • 輸入中提示和游標每秒會送出好幾次用戶端事件,往返太慢就會跟不上使用者。
  • 聊天要從一位使用者到你的伺服器、再到 WebSocket 服務、最後送給其他人,每一段都加上它的距離。
  • 線上狀態(presence)在成員加入或離開時更新,更新太慢會讓已經離開的人看起來還在線上。
  • 重新連線發生在手機切換網路時,需要 TLS 握手加上重新訂閱,等於連續好幾次往返。

每一段都要算

當你的後端發布一則事件,它會經過:

  1. 從使用者的操作到你的後端,
  2. 從你的後端到 WebSocket 服務的 HTTP API,
  3. 從 WebSocket 服務到每一個訂閱的用戶端。

第 1 段和第 3 段取決於使用者在哪裡,第 2 段取決於你的後端在哪裡。如果後端在東京、WebSocket 服務在維吉尼亞州,兩位台北使用者之間的一則事件就要橫越太平洋兩次。把 WebSocket 服務放在使用者附近,可以的話,也把後端放在它附近。

使用者送出訊息
①
你的後端
② HTTP API
WebSocket 服務
③ WebSocket
每位訂閱者
每個事件都要走三段路;第 ① 與第 ③ 段取決於使用者在哪

自己量量看

量測範圍: pusher:ping/pusher:pong 量到的是已建立 WebSocket 連線到即時服務端點的往返時間,不是事件從後端發布到訂閱者收到的完整延遲,也不包含首次連線時間。後端、廣播分發及瀏覽器渲染都不在這項量測內。建議在實際使用者網路(包含行動網路)多次測量,觀察中位數和 p95;DNS、TLS、路由與壅塞也會影響結果。

表格只是起點,真正重要的是你使用者的網路。Pusher 協定有一個 ping 訊息,伺服器收到會立刻回應,所以不用發布任何事件就能量往返時間。把下面存成 ping.mjs,用 Node.js 22 以上的版本執行(內建 WebSocket):

// node ping.mjs YOUR_APP_KEY [host]
const [key, host = 'ws.hibira.io'] = process.argv.slice(2);
const ws = new WebSocket(`wss://${host}/app/${key}?protocol=7&client=js&version=8.4.0`);
const times = [];
let sent = 0;

const ping = () => {
  sent = performance.now();
  ws.send(JSON.stringify({ event: 'pusher:ping', data: {} }));
};

ws.onmessage = (msg) => {
  const { event } = JSON.parse(msg.data);
  if (event === 'pusher:pong') times.push(performance.now() - sent);
  if (event !== 'pusher:connection_established' && event !== 'pusher:pong') return;
  if (times.length < 20) return void setTimeout(ping, 250);
  times.sort((a, b) => a - b);
  console.log(`best ${times[0].toFixed(1)} ms, median ${((times[9] + times[10]) / 2).toFixed(1)} ms`);
  ws.close();
};

從使用者所在的地方執行:在你服務的每個國家各開一台雲端主機,或請同事用家裡的網路跑一次。要比較的是中位數;最佳值則顯示路由離下限有多近。行動網路還會再增加延遲,所以也用手機熱點測一次。

想看真實使用者的數字,可以記錄 pusher-js 從 connecting 到 connected 花了多久,連同使用者的國家一起送到你的分析工具。

我們的規劃

Hibira 目前在香港,因為它大約位在東亞和東南亞的中間。下一個準備中的區域是東京,再來是新加坡。每個應用程式會固定在一個區域,你可以選離大多數使用者最近的那一個。

如果想在切換之前先看看你自己使用者的數字,免費方案就足以對你自己的應用程式執行上面的腳本。