← 博客

在亚洲做实时功能:WebSocket 服务器放在哪里很重要

· Hibira

为什么距离决定了实时通信延迟的下限、从香港到亚洲各地的下限是多少,以及如何测量你自己用户的往返时间。

聊天消息、报价跳动或“对方正在输入”的提示,只要在大约十分之一秒内送达,感觉就是实时的。这段时间大部分花在网络上,而网络时间大多由你选择的一件事决定:WebSocket 服务器放在哪里。

距离决定下限

光在光纤中每毫秒大约前进 200 公里,约为真空中光速的三分之二。所以到 1,000 公里外服务器的一次往返,至少要 10 毫秒,这还没算路由、排队和处理的时间。实际的海缆不会沿着大圆走,路由器也会增加延迟,所以实测的往返时间常常是这个下限的 2 到 4 倍:我们从香港到台北实测约 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 目前在香港,因为它大约位于东亚和东南亚的中间。下一个准备中的区域是东京,然后是新加坡。每个应用会固定在一个区域,你可以选离大多数用户最近的那一个。

如果想在切换之前先看看你自己用户的数字,免费套餐就足以对你自己的应用运行上面的脚本。