跨数据中心的 TCP Socket 优化
On 2014-03-03 22:57:15 By Soli[译]跨数据中心的 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