我们为什么使用Linux内核的TCP协议栈?或者内核旁路?

最近的一篇文章提出了“我们为什么使用Linux内核的TCP协议栈”的问题,并在Hacker News引发了非常有意思的讨论。

在CloudFlare的时候我也曾思考这个问题。我的经验绝大部分来自于在这里和生产环境中成千的机器打交道,我可以试着从这个角度回答这个问题。

让我们从一个更宽泛的问题开始 – 到底什么才是运行一个操作系统的意义?如果你打算运行一个单独的应用,那使用一个包含几千万行代码的内核听起来就像一个负担。

但实际上,我们绝大多数的人都决定要使用某种类型的操作系统,并且我们这么做主要有两个原因。首先,操作系统层带来了硬件的独立性和容易使用的API。有了这些我们可以专注于编写适用于任何机器的代码 – 而不只是我们目前所拥有的特定的硬件。其次,操作系统添加了一个时间分享的层。这让我们能同时运行多个应用。不管是一个额外的HTTP 服务器还是一次Bash会话,这个能在多个进程之间共享资源十分重要。所有被内核暴露出来的资源都可以在多个进程之间进行共享。

用户空间网络

对于网络栈来说并没有什么不同。通过使用通用的操作系统网络协议栈我们拥有了运行多个网络应用的能力。然而如果我们为了运行一个用户空间的网络栈,将网络硬件专用于一个应用程序的话,这个能力就丢失了。通过要求将网卡仅为一个进程使用,你就失去了和你的服务器同时运行的能力另外一个应用,如一个SSH会话,的能力。

可能这听起来有点不可思议,但是这正是绝大多数的用户空间网络栈技术的目的。用来完整描述这个名词叫做“完全内核旁路(full kernel bypass)”。这想法是绕过内核,然后直接从用户空间的进程使用网络硬件。

在Linux的生态环境中,有一些已经存在的技术。不是所有的都是开源的:

  • PF_RING

  • Snabbswitch

  • DPDK

  • Netmap

我已经在之前的一篇文章讲过它们。所有的的这些技术都需要将网卡整个移交给一个单独的进程。换句话说:完全有可能编写你自己的网络协议栈,让他变得聪明,专注于高级的特性,并且针对性能进行优化。但是这会招来一个很大的开销 – 每一张网卡最多只能将它用于一个进程。

关于虚拟网卡(VF)这里有一个小小改变,但是我们不要在这里讨论它——它无法工作。我已经在“虚拟化方案”的那一段中讲到了它。

但即使有种种障碍,我也不能简单地否定内核旁路的好处。确实有很多人在运行自定义的网络协议栈,并且他们可能出于下面两个原因之一:

  • 延迟

  • 性能(更低的CPU消耗,更高的吞吐量)

延迟对于高率交易(high frequency tradin)来说至关重要。交易者能支付得起自定义的硬件,和各种复杂的私有网络栈。然而我来说,运行一个闭源的TCP协议栈会让我很不自在。

在CloudFlare的内核旁路

https://blog.cloudflare.com/kernel-bypass/

https://blog.csdn.net/wwh578867817/article/details/50139819

尽管这么说,在CloudFlare我们也的确使用内核绕行。我们是上面提到的第二种人,我们很在乎性能。更加明确的说,我们遭受着IRQ风暴的问题。Linux的网络栈一秒钟能处理的数据包是有限的。当达到上限的时候,所有的CPU都忙于接收数据包。在这种情形下,数据包要么被丢弃,要么会导致应用CPU匮乏( starve of CPU)。尽管在正常的情况下我们不用处理IRQ风暴,但是当我们是一次三层(OSI中的第三层)DDoS攻击的对象的时候就会成为问题。这是一种攻击目标被各种随意的不属于任何合法连接数据淹没的攻击 – 通常来说都是欺骗数据包(spoofed packets)。

在一些攻击中我们一台服务器会遭受一秒高达3Mpps的数据包。一个广泛适用的法则就是Linux的iptables能在一个不错的服务器上处理约1Mpps的数据包的同时,仍为应用留下足够多的CPU。这个数字还可以通过调优来提高。

对于这种规模的攻击来说Linux内核对于我们来说是不够的。我们必须找到解决方法。我们没有使用之前提到的“完全内核旁路”,相反的我们使用的是我们自称为的“部分内核旁路”。通过它内核仍然保留网卡的的拥有权,并且能让我们只在一个单独的“接收队列(Rx Queue)”上执行旁路。我们在SolarFlare的网卡上使用Solarflare的EFVI API。为了支持Intel的网卡,我们给Netmap添加了一个部分内核旁路的特性:这个在这篇文章里有讲到。通过这个技术我们可以将我们的抗DDoS iptable分担到一个十分快的用户进程。这让Linux不用再处理攻击的数据包,因此避免了IRQ风暴的问题。

一个完全的用户空间TCP协议栈如何呢?

我们的同事经常会问我:我们什么我们不用Solarflare的OpenOnload框架来运行我们的Nginx呢,使用十分快速的用户空间TCP?

是的,这会更快,但是没有证据这能产生什么实际上的不同。我们服务器绝大多数使用的CPU被用在了用户空间的Nginx进程而不是操作系统。CPU主要用在经常的Nginx的簿记和我们的Lua应用逻辑,而不是处理网络。我估计通过旁路我们能节省大约5%-10%的CPU,这(至少在目前看来)并不值当。

其次,为了Nginx使用内核旁路会干扰我们平常使用的调试工具。我们的systemtap脚本会变得没有用。Linux的netstat统计会停止记录重要的事件,并且tcpdump不会再工作。

然后我们的DDoS缓解栈来说也有一个问题。我们是iptables的重度用户,正如我们在这个BlackHat的演讲中讲到的。自定义的TCP协议栈是没有入”hashlimits”和”ipsets”这类东西的。

但是不只防火墙的特性。Linux的TCP协议栈有一些特别有用的非常重要的支持,如RFC4821,可以通过用sysctl的sys.net.ipv4.tcp_mtu_probing来设置。这种支持当一个用户在一个ICMP的黑洞后面来说是非常重要的。可以通过阅读这个文章来了解PMTU。

最后,每一个TCP协议栈都会有一些自己的问题和怪异模式。我们已经描述了三种Linux TCP栈中不是很显而易见的Linux怪异问题:

  • 读缓冲中垃圾回收器闯入的问题

  • 关于太多监听套接字的问题

  • 一个套接字可写意味着什么

想像一下要在一个闭源或者年轻的TCP协议栈中要调试这种问题意味着什么。

结论

  有两个主题:第一,目前还没有稳定的开源的部分内核旁路技术。我们希望Netmap能抢占这个商机,并且我们在积极地通过我们自己的补丁来支持它。第二,Linux TCP协议栈有很多重要的特性和优秀的调试能力。需要花费几年的的时间来能挑战这个丰富的生态系统。

  鉴于这些原因,基于用户空间的网络是不大可能成为主流的。在实践中,我只能想象少数合理的应用需要用到内核旁路技术:

  1、软件的交换机或者路由器。这里你想将网卡交由应用来处理,让它们来处理原始的数据包并且完全绕开内核。

  2、专用的负载均衡器。类似的,如果该机器只用来做数据包的随机处理( packet shuffling ),那绕过内核就是合理的。

  3、对于选定的高吞吐/低延迟的应用进行部分旁路。这就是在我们的DDoS缓解系统中用到的。不幸的是,我尚不知道有什么稳定且开源的TCP协议栈是适合这一类的。

  4、对于普通用户来说,Linux的网络栈才是正确的选择。尽管这比起自己重写TCP协议栈来说不那么振奋人心,但是我们专注于理解Linux栈的性能并且修复其中问题。已经有一些正在进行的严肃的提议来改善这个年老优秀的Linux TCP协议栈。

猜你喜欢

转载自www.cnblogs.com/huangfuyuan/p/9238437.html
今日推荐