UCloud 网络在吞吐量大时 PING 包延迟变大的问题

背景

我在 UCloud 上有云服务器,带宽开了 2Mbps,绝大部分静态资源都放在 CDN 上,再加上 gzip 压缩,2Mbps 的带宽已经够用了。在实践中,大部分情况下,2Mbps 的带宽确实足够使用了。

我为什么要说大部分情况下呢?因为还有一小部分情况 2Mbps 的流量是不够用的,那就是在用户突然涌入的时候,2Mpbs 的带宽会无法应付突然出现的大量流量。其实就正常来说,在这种情况下,2Mbps 虽然不够用,但用户感觉也就是网站加载速度有点慢,最多花个四五秒也就能完全加载下来了,并不会有什么大的问题,所以 2Mbps 对我们来说也是足够的。

然而,UCloud 的网络却存在一个严重的问题,导致 2Mbps 的流量对我们来说并不够用,由此我们遇到了一个难以发现的同时又比较奇怪的问题。这个问题的出现几乎无规律,几乎是随机出现的,唯一的一点规律就是似乎在上网高峰期的时候出现概率会大一些。

问题的出现

首先从阿里云的监控说起。

我们使用阿里云对 UCloud 的云主机进行 PING 监控,当 PING 丢包率超出阈值的时候,我就会收到告警短信。然而,我收到告警短信的频率似乎有点高。最开始的时候,我收到告警短信的时候还会立刻打开我们的网站去看看有没有什么问题,但是没有一次发现网站有异常。PING 告警短信的次数多了,我也就麻木了,这时候的我认为是阿里云的监控自己抽风了。

后来,随着网站用户的增加,我们观察到网站在某些时候会出现无法加载的情况,但是这种情况持续不了一两分钟,随后会立即恢复。由于这种情况出现基本无规律,而且每次都能迅速恢复,影响也不是很大,我们也没有深究。

后来,这种情况出现的次数多了,我们也开始关注起这个问题来。在这个问题再次出现的时候,我很常规地 ping 了一下我们的服务器,结果惊奇地发现 PING 响应时间竟然在几千毫秒以上,甚至超过了 5000ms,而包却一个没丢。显然,网站无法加载肯定就是由于 ping 延迟太大了,TCP 握手以及 SSL 交互消耗了大量时间,最后导致一个页面要花上几十秒才能加载出来。对用户来说,这网站显然就是没法打开的状态了。

猜测原因并验证

虽然观察到了这个现象,但是还不知道造成这个现象的原因是什么。考虑到前不久 UCloud 出的那次年会光缆撞断事件,我们对 UCloud 技术的认可其实已经下降了很多,我们也就暂时将这种情况归结于 UCloud 的问题。后来的调查发现,这锅确实应该由 UCloud 来背。

观察到这个现象大概过了几周,我脑袋里灵光一闪,突然想到 TCP over TCP 时也会有类似这样 PING 包延迟变大但没有丢包的表现,结合 UCloud 使用了自己的虚拟局域网来保证各个数据中心之间的互联互通的情况,看来我大概知道出现这个问题背后的原因了。

有了猜想之后就要验证,验证很简单,我们的 UCloud 主机只有 2Mbps 的上行带宽,而我用的是电信百兆宽带轻轻松松就可以把云主机的上行带宽跑满,于是我就进行了下面的操作:

  1. Ping 我们的云主机,可以观察到延迟稳定在 40ms 左右
  2. 使用 scp 从云主机上复制一个大文件到本地,然后继续观察 Ping 的延迟

当我开始从云主机下载文件的时候,云主机的上行带宽迅速被我占满了,这时候 Ping 延迟开始稳步增大,大概每过一个包就会增加几百毫秒的延迟,同时这个延迟还在不断增大,最后成功破万。坑爹呢这是!上万毫秒的延迟这还能用吗!

与此同时,我另一个终端中开启的到云主机的 SSH 连接的反应变得非常迟钝,按一个键要过几秒钟后才有反应,SSH 终端也几乎没法用了。

当我手动停止 scp 下载进程的时候,Ping 迅速回到了 40ms 的范围,同时附加十几个一起到达的 Ping 回包。

那么 Ping 延迟变大的原因就找到了,只要把上行带宽跑满,Ping 延迟就会变大,虽然占据了上行带宽的那个连接的吞吐量不会受影响,但其他连接则会受到严重的影响。

为了证实这是 UCloud 的锅,我在青云的云主机上也进行了这样的测试。同样是 2Mbps 的带宽,当我把主机的上行带宽跑满时,Ping 响应虽然有所增大,但撑死也就 100 多毫秒,下载连接以外的 TCP 连接几乎不受任何影响。

由此可见,这锅确实要由 UCloud 来背。

现象后面的原因

出现这种现象的原因可能有几种:

  1. 使用了 TCP over TCP 构建数据链路,或者使用了类似的方法(在可靠的传输协议上再使用可靠的传输协议)
  2. 实施流量控制时方法不当或参数设置不当
  3. 在链路中使用了超大容量的缓存

第一种情况,可参见此文(英文):http://sites.inka.de/bigred/devel/tcp-tcp.html 。文章大意是,不要在 TCP 之上使用 TCP 协议,如果这么做了,下层的 TCP 发生重传时,会迅速地将错误扩散到上层 TCP 上,于是上层 TCP 也会发生重传,这就给下层 TCP 本就糟糕的网络增加了更大的负担,结果下层 TCP 会发生更多的重传,最后恶性循环导致整个网络不可用。用户看到的表现就是 Ping 延迟很大,但一个包都没有丢。

第二种情况,就是 UCloud 在实施流量控制的时候没有设定正确的参数,或者在整个流控结构上规划有问题。详细的情况比较复杂,要知道细节可以参考 linux 流量控制(tc 命令)的文档。不恰当的流量控制设置会变成下面第三种情况中要描述的“超大缓存的节点”。

就目前掌握的信息来看,我比较倾向于是 UCloud 实施了错误的流量控制或流控参数设置有误导致的此问题。

第三种情况,则是网络中的存在超大缓存的节点。比如说,路由器在繁忙的时候会将不能立即处理的包缓存起来,等待线路空闲之后再转发出去。这个缓存空间的大小一般都不会很大,所以造成的延迟也不会很大。但如果设定了一个很大的缓存,比如线路物理带宽只有 100Mbps,却设置了一个 1Gbit 大小的缓存,那么缓存中缓存的包最快也要 10s 才能完全清空。如果缓存满了,而线路又一直繁忙,那所有的包就等着 10s 后才能被转发出去吧。当然这是一个极端的例子,现实世界中不会有人这么干的。

解决方法

最佳的解决方法是使用 TCP BBR 拥塞控制算法,这是谷歌提出的一个 TCP 拥塞控制算法。这个算法的最大特点就是,分别估算网络的延迟和吞吐量,而不是同时估算。这种做法对网络上的缓存节点效果拔群,理论上来说,对我们目前面临的这种情况也有疗效。

次佳的解决方法是自己在云主机上实施流控,将出站流量限制在比 UCloud 分配的上行带宽小一点的水平上,这样可以保证出站流量不会跑满 UCloud 的带宽限制。这样,无论 UCloud 那边存在什么问题,我们都可以保证不会触发这个问题。唯一的缺点就是浪费了一点上行带宽。

最后我们解决这个问题的过程有点狗血,服务器的已经很久没有重启过了,内核还停留在老掉牙的 3.10 版本,要使用 BBR,至少要升级到 4.9 版本,于是我们不得不安排时间重启服务器。

根据分时流量统计,我们决定在凌晨一点重启服务器。在输入了 init 6 之后,我就等着旁边的 ping 重新收到回包……然而,五分钟过去了,ping 依然没有复活,这时间似乎有点长啊。

于是我从 UCloud 的后台登录到云主机的终端(中间省略重启、关机、再重启等常规过程),结果发现主机卡在某个奇怪的引导页面了。好吧,现在只好再次重启,到了 grub 菜单后,手动选择 4.9 内核,回车后终端上打印了两行看起来很正常的信息之后就再也没有反应了。这情况,估计是内核恐慌(kernel panic)后连终端都挂掉了。

没有办法,只好再次重启,到了 grub 菜单后手动选择 4.8 的内核,这回倒是很快地就启动成功了。

考虑到停服时间已经有点长了,就决定暂时先用着 4.8 的内核,虽然不能使用 BBR 了,但起码还可以用 tc。当天中午,我就使用 tc 进行了这样的限速:


tc qd add dev eth0 root handle 1: tbf latency 300ms burst 4096 rate 1.95mbit

这是我经过测试之后可以在我们的云主机上获得的最佳带宽和延迟的参数组合,burst 设为手册推荐值的两倍,带宽设为低于 ucloud 带宽 0.05mbit,latency 设为尝试出来的经验值 300ms,这样可以保证在吞吐量几乎达到 2mbps 的情况下,延迟保持在 300+ms 左右。这样的延迟是完全可以接受的。

虽然理论上来说使用 BBR 的话,可以在不限速的情况下解决 UCloud 的这个问题,但毕竟没有验证过。由于生产服务器不能随意折腾,所以也不知道实践中 BBR 到底能不能解决 UCloud 的这个问题。

后来我发现其实我们在 UCloud 上还有一台不怎么使用的云主机,于是我先把这台主机升级到 4.9 内核,然后验证了一下在这台主机上也能重现带宽跑满后 Ping 延迟变大的问题,然后将 TCP 拥塞控制算法改为 BBR,再次进行测试,结果表明 BBR
确实可以解决此问题,带宽能跑满不说,Ping 延迟也基本能够在 100ms 以内响应,这显然要比使用 tc 做流控要好。

所以,如果遇到这个问题的话,能上 BBR 的就先上 BBR,不能上 BBR 的就在本机做流控。向 UCloud 提工单是没有用的,因为我已经给 UCloud 提过工单并给他们分析了可能的原因,然而他们却不打算修复,他们回复的大概内容是:

  1. 我们测试过,让 Ping 延迟增大比直接丢包效果更好
  2. 所以我们不会解决此问题
  3. 建议您升级带宽

总而言之就是反正我们就这样,爱用不用。当然我对他们所说的“让 Ping 延迟变大比直接丢包更好”这一观点持反对态度。IP 层就应该丢包,如果使用大缓存去缓存 IP 包,反而会让现在常见的 TCP 拥塞控制算法不知所措,丢包的事情交给 TCP 去处理就好,IP 层不要越俎代庖。

反正我们下一台主力云服务器不会选择 Ucloud 咯,最多也就用 UCloud 做个异地热备。

后记

经过调整后,我再也没有收到阿里云发来的 Ping 丢包告警短信了。从阿里云的 Ping 监控图表来看,网络状况也得到了很大的改善。

看图说话,服务器重启是在 3 月 23 日凌晨进行的,限流操作则是在当天中午进行的,可以看到经过处限流处理后,Ping 丢包都变得比较正常了,Ping 响应时间也大多落在 100ms 以下了。

ucloud 云主机限流后 ping 监控变化

本文发表于 技术向,并添加了 , , , , , , 标记。保存永久链接到书签。

发表评论

电子邮件地址不会被公开。