[译]跨数据中心的 TCP Socket 优化

最近,我有个很好的机会工作在跨不同数据中心的机器上。任务很简单:我们要把存储在 Kafka 中的数据,从一个数据中心复制到另一个数据中心。 Kafka 是在 Linkedin 开发的一个 消息系统。经过我们测量,发现通信吞吐量极其低。即使在两个数据中心之间有 1Gb 带宽的 连接,我们也只能以不到每秒几 Mb 的速率来复制数据。我们马上意识到这可能是由于数据中心 之间的高网络延迟导致的。在 TCP 协议中,窗口大小控制着 Socket 在等到接收端发来的 确认之前,可以发送多少字节的数据。当客户端和服务端之间存在高延迟的时候,我们需要把 窗口大小设置的足够大,以分担每个确认的开销。在两个数据中心之间一个来回的延迟大约是 80ms ,这允许我们发送差不多 1Gb * .08 / 8= 10MB 。为了更好地利用带宽,我们把 TCP 的窗口大小设置为 1MB ,来代替原来的 64KB 。然而,吞吐量一点也没提高。

经过一番研究,我们意识到问题出在我们设置窗口大小的方式上。 TCP 协议最初所允许的 窗口大小的最大值只有 64KB 。后来,在高延迟的网络上对更大的窗口大小的需求突显出来。 然而,再改变指定窗口大小的 TCP 头部为时已晚。作为一个折衷的方案, TCP 协议经过 扩展,支持了窗口缩放系数。这个窗口缩放系数使得窗口大小可以被解释为原来的 2^14 倍。窗口缩放系数并不是 TCP 头部的一部分。它是在建立 TCP 连接时 的初始握手中协商的。因此,为了把 TCP 的窗口大小设置为一个大于 64KB 的值,你必须在 socket 连接建立之前设置它。否则就太晚了。 Kafka 中的 socket 代码原来看起来像 这样:

channel = SocketChannel.open()
channel.connect(address)
channel.socket.setReceiveBufferSize(bufferSize)

因为 setReceiveBufferSize() 是在 socket 连接建立之后才被调用,所以它并没有对 改变 socket 的窗口大小起到任何作用。于是,我们改变了最后两条语句的顺序。我们复制的 吞吐量立即提升了 10 倍多。 最后,我们还加了一条 channel.socket.getReceiveBufferSize() 来验证窗口大小 确实被 TCP 接受。


本文翻译自 Optimizing TCP Socket Across Data Centers

Creative Commons License Except where otherwise noted, content on this site is licensed under a Creative Commons Attribution 4.0 International license .