DPDK介绍及分析
什么是DPDK
Intel® DPDK 全称 __Intel Data Plane Development Kit__,是intel提供的数据平面开发工具集,为Intel architecture(IA)处理器架构下用户空间高效的数据包处理提供库函数和驱动的支持,它不同于Linux系统以通用性设计为目的,而是专注于网络应用中数据包的高性能处理。其工作在用户层,取代传统Linux系统中的网络数据报文处理。但需要注意的是,DPDK提供的是高性能处理报文的能力,而不是对报文的处理。这也提供了我们自定义用户协议栈的能力。
因此,任何基于DPDK的应用开发都需要自建协议栈,或者使用基于DPDK的协议栈。目前比较有名的基于DPDK的协议栈有MTCP,DPDK-ANS等。这里不作讨论。
DPDK的优势
使用linux下的AB发送POST请求压测基于DPDK(MTCP)的lightppd服务器和基于Linux的lightppd服务器。不同大小的数据结果如上所示。DPDK的吞吐达到了Linux的2倍。值得一提的是,CPU的消耗具有迷惑性,后面会提到DPDK采用轮询的方式,我们的机器上有24个逻辑核,我分配了2个核给DPDK独占。也就是说,并不能认为DPDK消耗的CPU是基于Linux的2倍。__事实上,无论数据多少,DPDK在我们的测试环境下只会占用8%左右的CPU。__
C10K Problem
It's time for web servers to handle ten thousand clients simultaneously, don't you think? After all, the web is a big place now. —— Dan Kegel
C10K 问题狭义的说,就是如何同时承载1W+的客户。从per client per thread 到 many client per thread,从阻塞式IO到IO复用,异步IO,从轮询事件模型到变更通知模型。这个问题就目前来看,似乎已经不再是一个障碍。但是考虑一下,C100K,C1M问题呢?似乎问题可以继续扩展下去。C10K问题的关键在于尽可能减少CPU等核心计算资源消耗,充分地利用服务器的资源。这些扩展问题,也可以在这些方面思考。但是epoll异步模型真的还适合C100K,C1M问题吗?
数据处理流程
以Linux为例,传统网络设备驱动包处理的过程如下:
- 数据包到达网卡设备
- 网卡设备依据配置进行DMA操作
- 网卡发送中断,唤醒处理器
- 驱动软件填充读写缓冲区数据结构
- 内核协议栈中处理数据包
- 通知用户层取包
- 数据到达逻辑层和业务层,进行处理
linux很明显设计的是通用的网络处理。但是,海量数据下中断是否真的有效吗?我们真的需要所有的协议栈吗?内核态用户态的内存拷贝是否可以避免,epoll模型是否有更好的替代?
中断 V.s 轮询
随着网络接口带宽从千兆到万兆,每一个报文都会触发一次中断的开销是巨大的。NAPI机制是一种解决这个问题的方法之一,它的核心概念就是不采用中断的方式读取数据,而代之以首先采用中断唤醒数据接收的服务程序,然后在使用轮询的方式一次性处理多个数据,直到网卡接收队列为空才继续打开中断。DAPI有一个极大的缺陷,它只适合大量的小数据,在数据大小过大时(即使使用DMA),会退化成中断模式。
DPDK 则使用纯轮询模式去解决这个问题,收发包不使用任何中断,但这不意味着DPDK不支持中断,毕竟实际应用中,存在网络的潮汐效应,低潮期轮询势必产生内核空转。因此DPDK同时也支持类似NAPI的机制,用户可以配置何时关闭中断,何时轮询结束等等。例如,在2张网卡的机器上,一般推荐,DPDK独占4个逻辑核。如果这不能接受,就可以考虑混合中断轮询的机制。
用户态驱动
Linux 中传统的 I/O 操作是一种缓冲 I/O,I/O 过程中产生的数据传输通常需要在用户态和内核态之间进行多次的拷贝操作。在某些情况下,这些数据拷贝操作会极大地降低数据传输的性能。应运而生的是一种叫做零拷贝的技术,采用直接IO,mmap/sendfile,写时复制等一系列策略,尽量避免 CPU 将数据从一块存储拷贝到另外一块存储,篇幅有限,这里不做过多阐述。
我们注意到,尽管DPDK实现的是Linux内核的功能,但是DPDK依旧是工作在用户态中的。应用数据通过DPDK传输是完全可以避免相互拷贝的。DPDK的包全部在用户空间,使用mbuf内存池管理。DPDK空间与应用层空间的内存交换不需要拷贝,只需要做控制权转移。
多核编程与无锁
多线程编程早已不是什么新鲜的事物了,多线程的初衷是提高整体应用程序的性能,但是如果不加注意,就会将多线程的创建和销毁开销,锁竞争,访存冲突,cache失效,上下文切换等诸多消耗性能的因素引入进来。这也是Ngnix使用单线程模型能获得比Apache多线程下性能更高的原因之一。
为了进一步提高性能,就必须仔细考虑线程在CPU不同核上的分布情况,这也就是常说的多核编程。多核编程和多线程有很大的不同:多线程是指每个CPU上可以运行多个线程,涉及到线程调度、锁机制以及上下文的切换;而多核则是每个CPU核一个线程,核心之间访问数据无需上锁。为了最大限度减少线程调度的资源消耗,需要将Linux绑定在特定的核上,释放其余核心来专供应用程序使用。DPDK就是采用的这种方式,不过因为DPDK工作在应用层中,即使设置了CPU亲和性,操作系统还是会对这些核进行调度。因此,DPDK推荐通过设置孤立核的方式来进一步减少竞争。同时还需要考虑CPU特性和系统是否支持NUMA架构,如果支持的话,不同插槽上CPU的进程要避免访问远端内存,尽量访问本端内存。这里不做过多描述
无锁编程,不管是之前提及的多核编程还是多线程编程,只要涉及到多个执行单元共享同一快数据的情况,一般都会需要同步技术。严格地说,无锁编程本质上是一种非阻塞性同步技术,所谓无锁,顾名思义,就是不需要用锁,使得所有操作表现出原子的语义。无锁一般来说,被人推崇的是其高于锁编程的效率,这里不过多赘述了。但是考虑一下,无锁真的无论如何都比锁编程效率高吗?有没有什么极端的例子呢。
访存开销(CaCHE & TLB)
我们知道内存的访问速度远远不如Cache的访问和CPU的频率的。当我们定义了一个数据结构或内存后,在内存中就有一个地址和其相对应,对于读,首先从内存中读入到Cache,最后送到寄存器中;对于写,则从寄存器写回Cache,最后通过总线写回内存。这份内存可能对应多个处理器中的Cache。任何一个处理器对该Cache的写,都会使得其他处理器Cache不一致,导致一次写回和Cache Miss。因此,DPDK在设计上,尽量避免多个核访问同一个内存地址,尽量避免共享导致的Cache不一致问题。(Cache不一致的本质是CPU独占CAHCE)
其次我们知道Cache是按照Cache line来构成的。即使我们给每个核分配各自独立的数据结构(常见的: struct Data datas[NUM_CORE];)。如果我们的 数据结构不是以Cache line对齐的,还是会导致一致性问题。因此,DPDK在定义多核各自的备份时,都是按照Cache line对齐的。
对于TLB,DPDK要求使用大页内存来减少TLB Miss,这可以适用任何程序。倒算不上DPDK的优化。TLB类似Cache,存放的是页表项。以4K内存页为例,应用程序需要使用2M的内存则需要使用512个内容页,也就是512个TLB表项。如果是2M内存页,则只需要1个TLB表项。
硬件加速
DPDK 毕竟是Intel®自己的产品总是可以在第一时间利用到Intel®硬件的优化。这也是在一些类似的产品中,DPDK最大的优势。
Intel® DDIO技术。它的核心在于,使网卡跟第三级Cache直接交换数据,绕过低速的内存交换。
硬件卸载功能,它是指将软件的某些功能下移到硬件实现。不同的硬件支持的功能也略有不同,在硬件支持的情况下,DPDK提供大致上有WLAN,校验和计算,TCP 分片,TCP收包聚合等等。
DPDK 转发模型
run2Completion 模型
我们在多核编程和流分类章节提到过,DPDK 倾向于单核单线程独占,每个线程对应一条网卡队列,这样可以极大的解决同步问题和Cache不一致,负载均衡问题。像这样,每个报文的生命周期都只会出现在其中的一个线程上,我们称为run2completion模型。这个模型有很高的扩展性,在上层业务逻辑很简单的情况下(比如,3层转发,DNS服务器),有很高的性能,然而一旦业务逻辑变得复杂,将存在处理流程上的依赖问题。其次这个模型,在调用DPDK的API上需要二次开发,最重要的,考虑到网卡队列数量的限制,能够利用的逻辑核不多。(在我们的机器上,2张网卡,每个网卡最多8条队列,那么我们最多只能利用16个逻辑核。在我们24逻辑核的机器上,有8个核无法使用。(BTW,实验数据告诉我们,网卡队列为2-4时,性能最好)针对这个问题,DPDK也支持流水线模型(Pipe Line)。
Pipe Line模型
Pipe Line模型主要思想是将不同的工作交给不同的模块,每一个模块只处理特定的事务。每个模块都有输入输出,通过这些输入输出将这些模块连接起来。特别的,这些模块可以位于同一个逻辑核上,也可以处于不同的逻辑核上,这依赖不同的业务实现逻辑了。这样,我们基于DPDK可以实现更复杂的业务逻辑。但尽管不同的逻辑核之间使用无锁队列来交换数据,多核之间依然存在Cache一致性的问题。但是相对的,这个模型可以充分利用物理机器的计算资源。
简而言之,Run2Completion模型适合业务逻辑简单的业务,Pipe Line模型适合业务逻辑复杂的业务。具体采用何种模型需要看具体情况。
其他优化技术
内存预读
我们这里主要是指软件的预读,通过使用汇编指令,在性能软件相关区域,把即将用到的数据提前从内存直接读取到Cache中,减少处理器等待的时间。DPDK在很多核心性能区域使用了这个技术。但Cache本身是有限的,我们在基于DPDK开发时,不能滥用这个指令,Cache过重反而有可能会降低性能,在实际开发中,需要不断权衡,充分测试。DPDK将这些汇编指令简单了做了封装,有兴趣可以参看DPDK源码。
分支预测
这个倒算不上什么稀罕技术,是一个比较普及的编程手段了。这个主要使用在频繁的条件转移上,一般情况下,处理器有50%的几率预测失败。在Tnet中就大量使用了这种技术来提高性能。不了解的同学可以看看http://stackoverflow.com/questions/11227809/why-is-it-faster-to-process-a-sorted-array-than-an-unsorted-array提出的这个问题,基本可以有一个简单了解,这里不深入提及。
- 充分利用CPU乱序执行能力
DPDK实践
- download dpdk.tar (如果是在虚拟机中开发,请按照http://dpdk.org/dev/patchwork/patch/945/ 中手动修复一个BUG)
- make config T=x86_64-native-linuxapp-gcc (require kernel-devel 。需要注意的是,请使用uname -r 查看系统版本。 保证yum 安装的版本跟内核版本一致,如果不一致请记得指定版本)
- 动态加载驱动 modprobe uio ;cd kmod & insmod igb_uio.ko ; (卸载 rmmod , 重载 remod)
- 设置hugepage (稍微设置大一些,其中超过1G的大页无法在开机后配置,请在linxu 启动脚本修改)。
- 卸载系统网卡驱动,绑定DPDK驱动 1.ifconfig xxx down 2.cd tool & ./dbdk-devbind --bind=igb_uio xxx 如果成功,再次使用 ifconfig 就看不到那个网卡了。注意,DPDK只会使用绑定DPDK驱动的网卡,不会影响其他未绑定网卡的处理流程。
- run testcase