https://www.yuque.com/dhcn/tn/xlk00g
这篇文章承接上篇Linux低延迟服务器系统调优,主要谈谈Linux网络IO的低延迟方案。由于本人经验所限,只使用过solarflare的软硬件方案,没用过其他的kernel bypass框架(如DPDK)或网卡,所以本文只局限于solarflare相关的使用经验。
首先声明下,本人和solarflare公司没有利益关系,本文尽可能客观的分享个人的使用感受。另外我也不是solarflare产品的专家,如有不准确之处还望指正。
solarflare的拳头产品是它的高性能网卡。我对solareflare网卡的第一感觉是“贵”,和一块高端服务器CPU的价格差不多了。因此如果只把它当成一块支持10G/25G网络的普通网卡来用是暴殄天物了,这里说的当成普通网卡指的是不使用它的软件方案,而是使用Linux内核实现网络编程,这样的话它并不会比普通网卡快多少。如上篇文章所言“一个对延迟要求很高(比如个位数微秒级延迟)的实时任务是不能触碰内核的”,solarflare网卡提供了配套的kernel bypass软件解决方案,还不止一个:Onload, ef_vi和Tcpdirect(关于这三个stack的用法这里就不细说了,官方文档说的很详细)。首先我们关心的是它们的性能怎么样?下图是官方文档中的一个测试结果:
Latency test results
测试使用了两台直接连接的软硬件配置相同的服务器进行pingpong测试,这样RTT的一半可以认为是一台机器从收到发的网卡到网卡的延迟。
从测试结果看来这个数字还挺吸引人的:10G网络可以低至800多ns。不过我认为这个测试方法有些脱离实际应用:首先测试中每次发送的数据都是完全一样的(payload全0),在ef_vi udp的测试代码中甚至出现了这种优化:初始化阶段就准备好了要发送的frame数据(包括ethernet头,ip头,udp头和payload),只要程序一收到数据就发送这个相同的frame出去,有点作弊的成分。另外,pingpong测试是不间断的收发,一秒钟会处理几十万个包,实际场景一般不会出现这么高频的情况。
因此,我决定实现一个更贴合实际的测试:echo测试,echo client和echo server程序分别在两台服务器上,echo client每次发送不同的数据,echo server收到后原样转发给client,这里echo server是under test的机器。echo server的收和发使用的是不同的协议:收udp multicast(模仿接收行情),发送tcp数据(模仿发送订单),因此echo server会先tcp连接到echo client。echo client控制发送频率:每秒1000个包,5秒结束。
通过测试不同的收发stack组合,以及各种配置选项,我所能达到的最佳延迟是960ns +/- 90ns(这里除去了第一个echo的延迟,因为这个简单的测试没做cache warmup,第一个延迟会高出5000ns左右)。最佳方案使用了最新的X2系列网卡(支持ctpio,比非ctpio快200ns),ef_vi接收udp,tcpdirect发送tcp,以及上篇文章提到的屏蔽中断的内核。这个测试主要使用16字节payload的小包进行测试,对于更长的包可以用1 byte = 1.5ns
来近似(亲测)。下面简单谈谈我对3个stack的使用感受。
onload
这是solarflare最经典的kernel bypass stack,属于transparent kernel bypass,因为它提供了兼容socket网络编程的接口,用户不需要修改自己的代码,只需preload libonload.so即可使用(类似使用tcmalloc)。由于其特别的易用性,十分适合新人入坑(先要买块网卡),是运动手表界的iwatch。
关于性能,在我的测试中onload比使用kernel的传统方法快了6000多ns,比ef_vi/tcpdirect慢300多ns,算是不错了。
另外提一下onload的配置,onload提供了非常多的配置选项,我在测试中测试了大部分相关的配置,最后发现对绝大多数配置来说使用默认值就好了,例外是设置EF_TCP_FASTSTART_INIT=0 EF_TCP_FASTSTART_IDLE=0
会稍微降低些延迟,这也和推荐的latency profile一致。
ef_vi
ef_vi是一个level 2的底层API,可以收发原始的ethernet frame,但没有提供上层协议的支持(不过能支持基于IP,proto,port的接收端filter)。除此之外ef_vi最大的特点是zero-copy:它要求用户预先分配一些recv buffer提供给ef_vi使用,网卡收到包后会直接写入这些buffer,用户通过eventq poll接口获得已填充数据的buffer id即可开始处理接收数据,处理完后再把buffer交还ef_vi循环使用。相比而言,onload无法做到这样的zero-copy,因为socket API只在用户开始接收数据时才提供buffer。
ef_vi API使用起来比较复杂,且缺乏上层协议支持,但能提供最好的性能,是专业用户的选择。我建议只使用ef_vi做udp的接收端,因为经filter后用户对包头的解析工作不多,这还有一个好处是,可以在用户程序中抓包(比如用于记录行情,后面会提到通用的本地抓包方法并不合适):通过一个ring buffer和一个写pcap文件的非关键线程,可以做到处理网络数据和抓包同时进行,而且抓包对处理数据的关键线程几乎零影响,这充分利用了ef_vi底层和zero-copy的特性。
tcpdirect
tcpdirect是基于ef_vi并实现了上层协议的stack,提供了类似socket的API:zocket,能让用户读写tcp/udp的payload数据,同时也继承了ef_vi zero-copy的特性。另外,tcpdirct要求使用huge pages。
我的建议是,对于udp的发送端和整个tcp使用tcpdirect。
最后,谈一个比较重要的话题:如何测量网卡到网卡的延迟?在这个echo测试中,由于echo server和echo client是不对等的的测试者(从收发的协议上),而且由于条件所限,两台机器的硬件配置有一定差异,也没有通过网线直连(中间经过了交换机),所以光看RTT不可靠,只能使用通用的网络延迟测试方法:
1)让交换机把echo server的收发链路数据镜像到另一个抓包设备。这种方法比较依赖交换机性能,精确度不高,比如抓包结果会出现因果乱序的现象。
2)通过tap或分光设备把echo server的网口数据分发到另一个抓包设备。由于条件所限,暂时无法使用...
3)在echo server本地抓包。这个比较依赖抓包工具,我进行过尝试,最后发现目前已有的工具都有其局限性,后面会详细讨论。
4)在程序中使用onload/ef_vi/tcpdirect提供的API获得包收发的硬件时间戳(网卡时间戳),这是我主要使用的方法,同时也参考了从echo client获得的RTT,用于double check前者的结果,另外可以测量各种抓包工具或者获取硬件时间戳造成的额外延迟。
本人经验有限,如果有更好的方法欢迎在评论区探讨。最后谈谈我使用过的本地记录网络包时间戳的方法:
tcpdump
这是大家用过的工具,通过kernel抓包,但对kernel bypass的方案无效,而且记录的是软件时间(系统时间戳),不等同网卡时间。tcpdump带来的额外延迟约为2000ns。
onload_tcpdump
顾名思义,可以对使用onload方案的网络数据进行抓包,但对其他无效,也无法获得硬件时间戳。onload_tcpdump带来的额外延迟约为1500ns。
solar_capture
solarflare自己软件抓包工具,需要额外购买license,不便宜,和网卡本身的价格差不多了,而且license绑定网卡。官方宣传solar_capture性能很高,可以记录收发包的网卡时间戳。我们曾经寄予厚望买了一个license试用,但发现局限性很大,基本个玩具。
首先,solar_capture不支持solarflare自己的最先进的X2系列网卡,只支持较老的7000/8000系列。关于这个问题我前几天有机会问了solarflare的一个system architect,他说更新solar_capture是一个较低优先级的事情...看来这工具本身在公司内部就是个边缘产品(官网上找solarCapture看到的都是硬件设备,这个软件工具的信息很难找到)。
另外,solar_capture不支持"ultra-low-latency"的网卡模式。这又是个硬伤,因为"ultra-low-latency"比其他模式快100ns左右(亲测),是我们默认会使用的模式。
最后,solar_capture对于一个网口的收和发的数据是通过两个任务来记录的,pcap文件中可能出现因果乱序现象(但时间戳是准确的),需要分析结果的用户自己处理。
solar_capture带来的额外延迟约为50ns。
通过ef_vi/tcpdirect API获取网卡时间戳
现在ef_vi和tcpdirect提供了获取接收和发送时间戳的API(获取发送时间戳是OpenOnload 201811新增加的功能),这样就可以在用户程序中直接测量网卡到网卡的延时了。获取两个硬件时间戳带来的额外延迟约为50ns,看上去和获取系统时间戳的开销差不多(相关文章:Linux获取纳秒时间戳的正确方式)。
sfptpd
这其实是solarflare提供的一个同步网卡时间源的工具,可以和网络上的其他设备同步,不过我主要用来和本地系统时间同步,这样可以测量网卡到用户程序,和用户程序到网卡的分段延迟。使用sfptpd的一个不方便的地方是这个服务必须一直运行才能保证同步的比较准确。