前言:代理客户端的绿点只会报平安,用fetch探测公网网站根本测不出真实链路健康状况。本文拆解广东电信10000测速页面隐藏的网络延迟抖动算法:十次采样取最小值当ping,剔除脏数据后用次优三项离差计算抖动,再参照RFC 3550做非对称平滑,并把这套算法搬进传输层Ping探测,实现延迟、抖动、丢包的真实链路健康度评级。
我扒了广东电信测速页面的代码,挖出一个藏了多年的算法
我被自己写的东西给骗了。
事情是这样的。我平时主力在用的代理客户端,就是我自己写的那个 PX/2。界面上一直有个小绿点,写着网络状态良好。我天天看着它,跟看自家孩子的成绩单一样,默认它是诚实的。
直到前两天晚上打一局游戏,ping 飙到两百多,团战的时候人物集体瞬移,我气得直接退了游戏,回头一看那个绿点。
还是绿的。
= =
那一刻我意识到一个问题,这个绿点根本不知道外面发生了什么。它判断网络好不好用的方式,是隔一会儿去 fetch 一下某个公网网站,能通,就是绿的。
这不是在测我的链路,这是在测「我、我的代理、跨国骨干网、目标网站的CDN」这条全家桶链路。目标网站那天 CDN 抽风,我的绿点照样绿。我的链路其实已经烂了,但只要对端还活着,它就继续给我报平安。
这测的不是网络,这是玄学。

所以我决定搞清楚一件事,到底怎么测,才能知道我和我服务器之间那条真实链路的健康状况。
我想起一个东西,广东电信的 10000 测速,就是 10000.gd.cn 那个页面,电信工作人员发过来的那种,广东的朋友应该都用过。我一直有个朴素的印象,运营商自己做的测速,算法上反而舍得下本。因为人家要拿这个数字跟用户对线,你宽带不达标,用户拿着截图来投诉,这个数必须经得起推敲。
于是我打开那个页面,F12,开始翻它的代码。
翻到一个叫 Speed.56ad6d0c.js 的文件,名字里带着个哈希,一看就是打包工具吐出来的产物,正常用户一辈子不会点开看第二眼。我顺着 pingtest 这个关键词往下读,读着读着,人就坐直了。
先说时延。你以为它会把十次 ping 取个平均?没有。它取的是最小值。
乍一听很离谱对吧,统计里最不靠谱的就是最小值,一个脏数据就能把你带沟里。但你想一下物理层面这件事,光在光纤里的传播速度大概是一毫秒二百公里。拥塞、排队、系统调度,只会让包变慢,不可能让光超速。所以十次探测里那个最小的值,就是这条链路物理光程的下限,是最接近真实的一个数字。其他所有偏大的样本,都是被路由器队列、CPU 调度这些杂音污染过的。
取最小值,不是偷懒,是把物理定律当成了滤波器。
我当时就愣了一下,这个思路真的很骚。

然后是抖动,也就是 jitter。这个更骚。
抖动这个东西,业界的算法五花八门。我顺手查了一圈,Speedtest 那一派用的是相邻两次延迟差值的平均,17CE 的方法论文档里写的也是「相邻延迟样本差值绝对值的平均值」,还有用最大减最小的极差法,还有用标准差的。极差法最惨,一个极端脏数据就能把结果带飞,你 CPU 卡了一下,抖动直接翻倍。
那电信是怎么算的呢。
它把十次采样的结果升序排个序,把最小的那个扔掉,取紧接着的第二、第三、第四名,这三个求平均,再减掉那个最小值,最后向上取整。
用它的真实数据演算一遍你就明白了。样本是 18.4、20.3、20.9、21.2、21.6、23.6、24.0、24.1、25.5 这么一串。最小值 18.4,直接当 ping 展示,取整就是 18。然后取 20.3、20.9、21.2 这三个次优的,平均下来 20.8,减掉 18.4,离差 2.4,向上取整,抖动就是 3。
你品品这个设计。为什么不取平均减最小?因为平均值会被那些排队的大样本污染。为什么不直接用相邻差值?因为样本少的时候相邻差值噪声太大。它取的是「次优的三项」,这几项既贴着物理极限,又互相印证过,是整批样本里最可信的一小撮。用最可信的一小撮和物理下限之间的距离来定义抖动,既排除了脏数据,又不会把真实的链路波动洗成零。
一个测速页面,把统计学里「用最可靠的子集估计总体」的思路用在了抖动上。我看完只想说,太牛逼了。
更绝的是,它还嫌这不够平滑。常态监控的时候用的是另一套基于 RFC 3550 的指数加权平均,而且是非对称的。抖动突然变大的时候,新样本权重给到 0.7,让警报快速拉起来;平稳的时候权重只有 0.2,让数值慢慢衰减回去。涨得快,落得慢,跟体温计有点像,量得快,退得谨慎。
对,RFC 3550,1996 年发布的那个 RFC,管实时传输的抖动估计。快三十年了,还在给今天的视频会议和测速页面打工。老归老,是真的能打。
好,算法我搞明白了。接下来的问题是,这套算法我能不能搬到自己的客户端里。
搬算法容易,难的是探测通道。说到这儿我得先替市面上那些用 fetch 测速的客户端说句公道话,HTTP 探测胜在哪儿都能用,不用改协议,对绝大多数产品来说够用了。但我的诉求跟它们不一样,我关心的是客户端和自建服务端之间那条加密通道的健康度。如果我也隔一会儿 fetch 一下 baidu 或者 google,那就又回到开头那个全家桶链路了,CDN 调度、跨国拥堵、服务器排队全混进来,测了个寂寞。
所以我把探测直接塞进了传输层里。
PX/2 的客户端和服务端之间,本来就有一条常驻的加密复用连接,业务数据全从这上面走。我在这条连接的协议里定义了两个控制帧,Ping 和 Pong,编号 0x0A 和 0x0B。测速的时候,客户端往这条已经活着的连接里塞一个 Ping 帧,服务端的控制协程收到后原样塞回一个 Pong,客户端用 Go 的单调时钟掐一下往返时间,完事。
没有 HTTP 头,没有 TLS 重新握手,没有浏览器主线程排队,跟业务数据走的是同一条物理路径、同一套加密载体。外面防火墙看到的就是这条连接正常的长度波动,连 ICMP 都不用发,也就不存在被路由器丢包或者限速的问题。
采样流程也照搬电信的。先发一轮预热,结果直接扔掉,因为第一轮往往混着路由查询、ARP 解析这些一次性的开销。然后密集打十轮,每轮间隔三十毫秒,超时五百毫秒算丢包。十轮跑完,最小值当 ping,次优三项离差向上取整当抖动,超时数除以总数当丢包率。

我把这十轮的结果画成了一个小小的柱状波形,放在界面顶部一个胶囊里,平时就显示 18 ms、抖动 3 ms 这种一句话,鼠标悬上去能看到丢包率和完整样本。点一下测速,半秒钟出结果。延迟、抖动、丢包三个数一凑,按一个五级评级矩阵给链路定级,绿点、蓝点、黄点、红点,各有各的物理含义,不再是拍脑袋的好或不好。
改完之后,我又跑了一把那个游戏。
绿点还是那个绿点,但这回团战一卡,我看见它诚实地跳成了黄色,抖动那一栏的数字在往上蹿。等网络缓过来,它又自己绿回去了。
那种感觉怎么说呢,像是家里那台一直只会报平安的摄像头,突然学会说真话了。
写代码写到后半夜,我盯着那个小小的波形图,突然觉得这个事儿挺有意思的。一个省级运营商沉淀了多年的测速算法,就藏在一个文件名带哈希、没有任何人会多看一眼的 JS 文件里。亿级用户每天用它测速,测完就走,没人知道那个抖动数字背后,是排序、剔除、取次优、向上取整这么一串讲究。而另一边,RFC 3550 里那个 1996 年的抖动公式,安安静静躺在几乎每一台视频会议终端里,没人谢过它。
好的测量方法不会过时,它们只是藏在没人看的地方,等一个较真的人把它们挖出来,接到新的管道上。
这大概又是一次磨平信息差。只不过这次磨的,是毫秒级的信息差。
/ 注意,本项目仅在 SyncMein 的500人小群分享。
/ 谢谢你看完了我的文章,我们下次再见吧。
/ 作者:明察
/ 投稿或爆料,请联系邮箱:mingcha@gqmg.com
















暂无评论内容