背景:
昨天调程序的时候遇到了一个 BUG,前端无法将文件正确传给后端,后端报错 EOFException(EOF 代表 End Of File)就是在程序尝试从一个数据流中读取数据时,发现已经到达了数据流的末尾,但它却期望还能继续读取更多数据时抛出的异常。
分析概括为:前端与后端建立了 TCP 连接,后端 tomcat 要从这个连接中读取信息,但一直没有读到就报这个错误了。
问题的起因令我发笑,分析了一堆代码,却忘了我前端服务器带宽 1Mbps,后端服务器带宽 3Mbps,导致我的整个 TCP 链路带宽只有 1Mbps,请求被限流,而后端 tomcat 在建立 TCP 连接后就一直尝试从连接中获取信息,迟迟不来就报 EOFException。
那就借此机会分析一下云厂商的服务器是如何实现带宽限制的吧
-
请求到达 nginx 反向代理获取目标地址会向目标地址(IP+端口)发送一个新的 TCP 链接,这个过程会经过 3 次握手,然后后端服务器会创建一个对应的 socket,这时候后端服务(tomcat)就进入了“备战状态”,准备接受和处理数据,且 tomcat 是知道被调用的接口是哪一个,信息在请求的 URI 中。
-
nginx 将文件分片数据通过建立的连接发送给后端服务器,图中会经过很多基础设施:边缘路由器、交换机、虚拟网络设备。
-
流量控制具体实现:
云厂商通常采用 流量整形 (Traffic Shaping) 或 流量控制 (Traffic Control) 的技术来实现对云服务器带宽的限制。这就像在您的服务器网卡前面放了一个“水龙头”,控制着流向服务器的数据流量的最大速度。
这个“水龙头”通常部署在云厂商的网络设备上,例如:
边缘路由器或交换机: 位于云厂商数据中心网络的边缘,靠近您的云服务器实例。
虚拟网络设备: 在虚拟化环境中,可能存在一个虚拟的网络设备层,负责管理虚拟机实例的网络流量。
带宽限制逻辑出现的时间点:
带宽限制的逻辑并不是在您的云服务器实例与外部建立 TCP 连接时才“出现”的。它是一个 持续存在且作用于所有进出该云服务器实例的流量 的限制。
更准确地说,当外部网络的数据包到达云厂商的网络基础设施,并且目标是您的 3Mbps 带宽的云服务器实例时,带宽限制的逻辑就会开始工作。
具体的工作流程(简化版):
外部数据包到达云厂商网络: 外部网络(例如互联网)的数据包通过云厂商的骨干网络传输,最终到达离您的云服务器实例最近的云厂商网络设备。
识别目标服务器: 云厂商的网络设备根据数据包的目标 IP 地址(您的云服务器公网 IP)识别出该数据包是发往您的特定云服务器实例的。
应用流量控制策略: 云厂商的网络设备会查找与您的云服务器实例关联的带宽限制配置(例如 3Mbps)。
流量整形/控制:
对于入站流量(进入您的服务器): 网络设备会根据 3Mbps 的限制,控制将数据包发送到您的云服务器实例的速度。如果瞬间到达的数据量超过 3Mbps,网络设备会将多余的数据包进行缓冲或延迟发送,直到平均速度不超过 3Mbps。
对于出站流量(从您的服务器发出): 类似地,网络设备也会控制从您的服务器发出的数据包的速度,确保不超过 3Mbps 的限制。
数据包转发到云服务器: 经过流量整形/控制后,数据包才会被转发到您的云服务器实例的虚拟网卡。
说简单点就是,云厂商的基础设施会存储我的云服务器 IP 以及其带宽限制为 3Mbps 的信息,当我的请求到达这些设备的时候会经过判断然后做一些相应的限流操作,这期间我的数据包会在这些网络设备中被缓存或者是被延迟发送,从而达到限流操作。
这个时候 tomcat 达到了时间限制,就会认为连接出了问题,就会抛异常如SocketTimeoutException
,在文件上传场景下可能是EOFException
。
云主机限制了带宽,也是一种限流,这令我想到了 Sentinel 实现的对微服务接口以及 Gateway 的限流,那就先分析一下 Sentinel:
-
微服务限流(Sentinel)是更细粒化、更上层的,目标是主机上的特定服务或接口,限制的是到达该服务或接口的请求速率。它是在请求到达服务进程后才开始判断和限制的。
-
Gateway 限流介于两者之间,它是一个独立的微服务,但它处理的是 HTTP 请求,可以基于 URI 等信息进行限流,比云厂商的 IP 级限流更细,但比 Sentinel 集成在业务服务内部的限流可能稍微粗一些(取决于 Gateway 的配置)。Gateway 的限流也是在请求到达 Gateway 服务后进行的。
两种限流机制的对比:
本篇文章很易懂但也很浅显,欢迎订阅合集一起探索网络的世界🎉