高性能服务器技术
[ 发表时间:2020-12-24 18:45:20 信息来源:九剑网络 ]
来源:https://zhuanlan.zhihu.com/p/332880645
从C10K问题到C10M问题
C10K问题是指单台服务器如何支持10K个并发连接。因为早期的网络服务器一般采用阻塞式I/O模型,使用多进程或多线程的方式来实现大量网络请求的并发处理,对系统资源的消耗较高,所以普通服务器难以支持大量并发连接。对此的解决方案是使用kqueue/epoll等I/O复用模型或IOCP/aio等异步I/O模型,在单个线程中处理多个并发请求,降低对资源的消耗,提高系统性能。典型的基于epoll的服务器有nginx、memcache、redis等。
随着半导体技术的发展,硬件设备和接口的速度有了极大的提高,可以承担更高的负载。另一方面,互联网应用越来越普及,服务器也需要处理更大量的数据,因此有了C10M问题,也就是如何让单台服务器支持千万级别的并发连接。
性能瓶颈
系统中断
操作系统一般使用中断方式来访问I/O设备,linux每收到一个报文,会触发一次硬件中断请求。由于中断处理函数需要消耗一定的CPU时间,服务器每秒钟能处理中断请求次数是有限的。一般服务器每秒钟大概能处理100K次中断请求,所以能接受和处理的报文数也难以超过这个范围。系统中断的开销是C10M问题的最主要影响因素。
这种方式对传统的慢速设备来说是比较匹配的,但访问高速设备时中断调用的成本就会变得很高,使得硬件设备的性能不能充分发挥。
系统调用和内核态的切换
现代操作系统一般都会区分用户态和内核态,应用程序需要使用一种特殊的中断指令从用户态进入内核态,以访问网络、硬盘等I/O设备。这个状态切换也是一种耗时操作,每次接收或发送报文都需要对应的一次系统调用和一次到多次的上下文切换,这也限制了服务器的性能。
内存拷贝
由于有用户态和内核态的隔离,在两者之间传递数据时需要进行内存拷贝,存在一定的开销。在高频的报文传输和处理过程中,这个开销也会变大,需要进行优化,尽量避免内存拷贝。
多核
随着CPU的单核性能逐渐接近瓶颈,服务器的整体性能越来依赖于多核和并行方面的优化。
早期linux的同步粒度很粗,例如使用一个全局的大内核锁(BKL-Big Kernel Lock),多核的并发性能很受影响,所以后来就逐步细化锁的粒度,对不同的数据用不同的锁,以提高系统的并发能力。
还有CPU的亲和性和NUMA内存架构等问题,对。linux内核中的各种数据结构,也由全局范围定义变成每个CPU核心定义一份。例如各种进程调度算法中,准备就绪队列都是每个CPU一个。
其它因素
还有内存分页大小、各种锁的使用等方面的因素。
解决方案
高速I/O场景下,最主要的瓶颈就是系统中断和系统调用、内核态切换等因素。这些都是操作系统相关的因素,所以说,对于C10M问题,操作系统不是解决方案,而是问题所在。
对于系统中断导致的的性能问题,可以通过轮询方式来解决。在低速I/O场景下,轮询会浪费CPU,中断方式更加高效。但在I/O非常频繁的时候,中断更慢,成本也更高,轮询反而更加快速高效。所以可以在I/O频繁的时候先采用轮询方式,如果轮询时没有结果,再切换到中断模式,这样既能实现高效的收发大量报文,又能在低速场景下降低CPU的负载。
至于系统调用和内核态切换方面的问题,一般有两种方法解决,一种是尽可能把操作放在内核中,像NFS/LVS等服务器,为了追求高性能,就采用了这种方案。不过内核代码开发和调试的难度更高,出错后影响更大,对一般的应用和服务器开发不太适合。另一种是采取内核旁路(Kernel Bypass)的方法,尽可能把操作移到用户空间中,绕开操作系统的屏障,高效的实现数据传输。
下面列举的几种新技术,从不同的角度提出了各自的解决方案。
DPDK(Data Plane Development Kit)
DPDK是Intel等公司开发的用于网络报文相关的开发工具包,可以在用户空间高效进行报文处理。以前主要是用于交换机、路由器、网关等应用场景,现在也用于更上层的应用。
DPDK的主要原理是通过Linux的UIO(Userspace I/O)技术,将网卡设备的访问和控制由内核外包给应用程序来实施。同时利用Linux的PMD(Poll Mode Driver),在高速网络传输过程中,使用轮询方式来访问网卡,避免频繁中断带来的CPU开销,而且还避开了系统调用和内核态切换以及相应的内存拷贝开销。
另外,DPDK还会通过其它一些优化手段来进一步提升系统性能,例如使用大的内存分页,来提高CPU TLB的命中率,使用无锁队列来提高并发性能等。
不过,DPDK也有一些缺点和不足。因为要在应用程序中直接访问I/O设备,所以原来在内核中实现的访问设备的代码,需要在用户态重新实现一遍。还有相应的TCP/IP协议栈的实现这不仅工作量巨大,而且完善代码细节都需要时间,导致在很多场景下应用时还存在障碍。
SPDK(Storage Performance Development Kit)
存储性能开发工具包,跟DPDK类似,主要用于存储设备的高速I/O传输。原理跟DPDK差不多,都是将I/O操作移到用户态进行,并采用轮询而不是中断的方式处理高频的I/O请求,从而大幅提升存储I/O的速度。缺点方面也跟DPDK一样。
io_uring
跟DPDK和SPDK不同,io_uring不是让应用层程序直接访问设备,而是采用一种非常大胆的方式来绕开内核态所造成的障碍。一般的I/O操作需要通过系统调用来发起,然后需要在用户态和内核态之间通过内存拷贝来传递数据,所以在高速传输时有一定开销。io_uring通过使用单生产者单消费者的环形缓冲区(ring buffer),直接在用户态和内核态之间共享I/O请求和操作结果相关的数据,这样就不用每次操作都触发系统调用和跨越内核态的内存拷贝,所以效率更高。再加上io_uring也支持轮询方式,可以大幅提高I/O速度的上限。参考网上的一些评测结果,在不使用轮询模式时,io_uring的性能大概比libaio提高10%,开启轮询模式后,性能可以提高50%到150%,接近甚至超过DPDK/SPDK的水平。
相比libaio只支持DIRECT_IO,io_uring可以更好的支持Buffer I/O。相比DPDK/SPDK,io_uring依然是在内核中完成I/O的处理,所以可以重用原有的设备驱动和TCP/IP协议栈、文件系统等,对上层应用开发的影响很小。像libev库已经增加了对io_uring的支持,使用libev的程序只要配置好参数,就可以启用,应用程序代码基本上不需要修改。一些开源项目,如nodejs、nginx、rocksdb等也在增加对io_uring的支持。
io_uring现在主要用于socket和文件,不过对其它I/O设备的支持也在开发和完善中,未来,io_uring应该会成为linux内核中通用的(异步)I/O处理框架,如果再结合现代编程语言里的协程或async/await语法,就比较完美了。
eBPF(extended Berkeley Packet Filter)
eBPF跟io_uring都是这几年Linux内核领域比较有革命性的技术。eBPF虽然跟C10M问题没有直接的关联,但在内核追踪(Kernel Tracing)、应用性能调优/监控、流控(Traffic Control)等方面能起到重要作用。它的具体原理是在内核中嵌入一个虚拟机,应用程序可以把编译好的eBPF字节码动态的插入到内核中运行,从而实现各种复杂多样的功能。举例来说,有以下几种情况:
1.基于eBPF的XDP(eXpress Data Path)可以在某些网卡上直接执行eBPF指令,快速检测报文内容,以决定是丢弃、转发还是接受,绕开内核协议栈对网络报文的冗长处理流程,从而高效实现类似netfilter/iptables的功能,还可以做一些防御DDoS攻击的处理。
2.eBPF的sockmap功能可以直接在内核中对网络流量进行灵活的转发,实现高速的网络代理功能,可以在Service Mesh等场景中高效的实现内部流量的转发和调控。
3.截获和跟踪内核中系统调用的执行过程,对特定的执行点生成事件通知。
4.监控并收集内核运行相关的统计信息,以方便进行性能分析。
eBPF跟AOP(Aspect Oriented Programming)有点类似,可以把程序逻辑从不同的维度进行解耦,把可观测性等相关的辅助逻辑从主体流程中分离出来,改为从外部动态的插入进去,既提高了灵活性,又降低了系统整体的复杂度。
eBPF的功能和应用场景还在不断发展,未来可能会作为一种更加通用的内核虚拟机,简化内核的设计,扩展内核的功能,就像js对Web引擎的作用一样。
硬件
前面说的都是软件层面的问题,最近十年,硬件技术也有了很多新的发展,给软件开发带来了新的挑战和机会。一方面,CPU的频率提升接近瓶颈,虽然靠增加核心数量和改进制造工艺,总体性能还在不断提升,但提升的速度已经不如以前。另一方面各种网卡、加速卡、外存设备和接口的速度却大幅提高,这样服务器系统的开发方式和模型也需要做相应的调整。
CPU/GPU/ASIC/FPGA
CPU是通用计算芯片,可以按照程序代码的指示,执行各种相应的操作,非常的灵活,但效率上有一定影响,这有点像我们人类,可以自适应的完成各种各样的工作,但效率不如机器。GPU主要用于向量和矩阵运算,比CPU多一点专用性。ASIC/DSP是针对具体应用场景设计出来的专用电路,就像是工业化流水线,高度定制,效率非常高,但只能生产某一类或者某几类特定的产品,灵活性受限。而FPGA则介于两者之间,既使用硬件电路来提高运行效率,又支持有限度的编程设置,以提高灵活度,就像3D打印和柔性制造,一方面部分保留工业制造的高效率,另一方面又增加了信息化的可定制性。
这几年CPU的性能提升不大,GPU/ASIC/FPGA等专用加速芯片越来越受重视,这就是常说的CPU Offloading,把CPU的负载分配给更专用和更有针对性的加速芯片,让宝贵的CPU时间可以用在更重要的工作上,以实现异构并行计算的目的。例如Intel在CPU中增加了用于AES加密的指令和相应的硬件电路。再如GPU中包含很多矢量计算的指令,可以加速图像处理的操作。还有智能网卡可以高效处理一些网络报文,减轻CPU的负载。像比特币矿机早期用CPU来挖矿,后来改成用GPU,最后改用专门运行HASH算法的ASIC电路,也是同样的道理。
为了提高计算性能,最有效的方法是做好分工,由不同的专用芯片来针对性的负责不同性质的计算工作,共同协作完成计算任务,这就是异构并行计算,也是高性能计算的趋势和方向。
高速网卡和智能网卡
在高速网络通信过程中,触发的硬件中断非常频繁,单个CPU核心难以承担中断处理的开销,CPU的多核也难以发挥作用。通过在网卡中提供多个缓冲区队列,可以让一个CPU核心专门负责一个队列,这样能把网卡的中断处理开销分摊到多个CPU核心上,提高整体的负载能力,满足高速通信的需求。
在云计算和SDN(软件定义网络)中,需要实现各种虚拟网络,对网络流量做灵活的控制,这虽然有利于简化网络的管理,但报文处理的性能上却不如传统的硬件网络设备。所以很多公司会使用基于CPU+ASIC或者基于FPGA的智能网卡,既能够利用硬件电路的高效率,又能在需要的时候进行升级,保证一定的灵活性。微软在Azure中就大量使用了基于FPGA的网卡,以构建内部的SDN网络。
NVMe接口
传统的机械硬盘速度较慢,在逐渐被固态硬盘代替。不过随着固态硬盘的速度大幅提升,原来的SATA+AHCI等I/O接口成为瓶颈,限制了固态硬盘的性能。因此就有了NVMe接口,在PCI-E的基础上,一方面缩短了CPU到SSD的指令路径,减少了跟CPU多核之间的同步加锁操作,另一方面,也增加了I/O命令队列的数量和深度,从AHCI的一个队列增加到NVMe的64K个,队列深度也从32个增加到64K个,所以不仅延时降低了很多,并行性能也大幅提高,提升了I/O通道的吞吐能力。
RDMA(Remote Direct Memory Access)
RDMA是一种远程内存访问技术,可以在不经处理器和操作系统的介入下,直接访问其它计算机的内存,实现计算机之间的数据通信和共享。原理上跟DMA类似,都是使用专门的芯片来负责数据传输的工作,减少CPU在I/O传输过程中的消耗,提高系统吞吐。而且这样可以绕开操作系统内核处理网络通信的协议栈,缩短处理的流程,避免内存拷贝,降低通信的时延。
RDMA有三种硬件实现
1.InfiniBand(IB)是专门为RDMA的硬件实现而设计出的方案,延时最低,速度最快,价格也最贵,主要用在超级计算机和数据中心里。目前的最新实现是4通道的HDR,带宽达到200Gbps,是市场上最快的端到端的互连技术。而12通道的HDR带宽可以达到600Gbps,在技术路线图中规划的NDR和XDR,使用12通道时带宽分别可以达到1.2Tbps和3Tbps,可以接近甚至超过系统内部通信带宽。
2.RoCE(RDMA over Converged Ethernet)是基于以太网实现的RDMA方案。支持RoCE的网卡可以直接基于已有的以太网络实现RDMA数据访问,既保留RDMA方案的高性能,又能复用已有的网络基础设施,成本比InfiniBand低,当然性能方面也要稍微差一些,而且也受限于以太网本身的性能。
3.iWarp(Internet Wide Area RDMA Protocol)是基于TCP协议的RDMA实现。缺点是性能比RoCE更低一点,优点是能够重用TCP/IP协议栈的丰富功能和特性,包括IP层的路由功能、TCP的可靠传输等。
异构芯片高速互连协议
并行计算很重要,但计算不是孤立的,计算过程中必然需要通信,两者往往是交织在一起,密不可分。并行计算中的任务分配和协作,都需要交换大量的数据和控制、状态信息,所以高性能计算也自然要求计算芯片之间的高速通信。
在计算机系统中,各个组件往往是通过系统总线来进行通信和数据传输,随着硬件性能的提升,系统总线从ISA进化到PCI、PCI-E,虽然能满足一般的内部通信要求,但对于CPU和加速芯片之间的高速数据传输,却有所不足,而且也会极大影响其它组件的通信。所以在计算芯片之间,开始逐步增加一些专用的数据传输通道。例如Intel的QPI和AMD的HyperTransport都可以在CPU内部实现多个核心之间全互连的通信,除此之外,还有各大厂商提出的一些芯片互连总线协议:
1.IBM推出的CAPI(Coherent Accelerator Processor Interface),不过除了IBM,其它支持CAPI的厂商较少。
2.业界一些大公司(包括AMD)联合推出的GenZ(Generation Z),支持的厂商众多。
3.CCIX(Cache Coherent Interconnect for Accelerators)是另一个开放的访存和I/O网络平台,有ARM的支持。
4.Intel主导推出的CXL(Compute Express Link),基于PCI-E,改善CPU和GPU、FPGA、网络等加速卡之间的通信能力,保证更低的延时和更好的缓存一致性。包括http://CXL.io、CXL.cache、CXL.memory三个子协议,分别针对CPU访问设备、访问系统内存、访问设备内存。
5.NVidia推出的NVLink,主要用于GPU之间,以及GPU和CPU之间的高速通信。不过目前Intel和AMD的CPU都不支持,只有IBM在自己的CPU中集成了对它的支持,这限制了NVLink未来的发展。
并行
前面列举了一大堆的技术名词,都只是介绍一下基本的用途和原理,其实对于相关的各种技术细节我也不是太了解。不过从中还是可以感觉到,对于高性能计算,最重要的就是并行,包括并行计算和并行通信。
异构并行计算
并行计算
之前Nvidia推出支持实时光线追踪技术的显卡,我看新闻时还有些感慨,觉得光线和光照这种非常普通的自然现象,显卡模拟起来居然这么费劲。后来转念一想,这也不奇怪,物理规律是同时对万事万物产生作用,天然就是并行的,所以显得毫不费力,而游戏则是仅凭CPU和GPU的计算来模拟整个场景,需要记录所有相关的状态和计算状态变换的过程,计算量非常巨大,模拟的效果自然会在细节上会有所不足。因此要构造复杂的场景,或者对复杂的系统进行模拟和控制,光靠提升单点的计算能力是不够的,还需要更多节点的并行计算。
并行计算可以说是高性能计算的核心和本质,是解决复杂系统问题的必由之路。例如超级计算机中会集成大量的CPU和GPU核心以及其它加速芯片,云计算的数据中心也是通过大量服务器组成的分布式系统来服务超大规模的用户。现实社会中的复杂问题也同样是通过并行的方式来解决的,例如,不管是传统工业的生产制造,还是软件行业中复杂系统的研发,都不是靠单个人的努力就可以完成,需要大量团队人员的协作,也就是大规模人力和智力的并行。不管是治理国家还是管理公司,都需要基于分治的并行,而社会生产和公司内部的职位、岗位划分,也是基于分工的并行。
异构计算
并行计算不只是多CPU和CPU多核的并行,更重要的是CPU和各种专用加速芯片相互协作的异构并行计算。异构计算的本质是分工的细化,目的在于更合理的分配计算任务,使得整个系统的性能和效率更加优化。就像公司在早期规模小的时候,老板事无巨细都要亲力亲为,等公司规模越来越大了,就需要有更细致的分工,针对每个人的特长,分配不同的任务。这也跟我们的社会生产从简单到复杂的发展规律相符合,从早期简单的社会分工和个体劳动、手工作坊的分工方式,到现代社会中的大规模工业流水线的细致分工。随着社会的发展,各个领域的需求会越来越复杂,所以不论是计算还是生产制造,未来也都会变得更加复杂,分工也需要更精细。
在各种计算芯片中,CPU的通用性和灵活性最高,性能相对较差,适合做控制协调工作,GPU适合图形图像处理和深度学习等场景。DSP/ASIC的性能更高,大规模生产的成本也更低,不过只针对特定应用场景,前期开发周期较长,灵活性不足,不适合小规模应用。FPGA则兼具灵活性和高性能,而且没有ASIC那样的前期投入,不过制造成本较高。一般来说,应用的规模越大,ASIC的平均成本越低,达到一定量级时,ASIC的平均成本会低于FPGA。所以经常会在方案还没有定型的研究、试验和小范围试用阶段采用FPGA,等定型之后需要大规模生产再使用ASIC。
这几年异构计算越来越受欢迎,特别是FPGA,在机器学习和高速智能网络设备等领域得到广泛应用,各大互联网巨头的数据中心里都集成了不少FPGA芯片。Intel和AMD先后收购了FPGA的两大巨头Altera和Xilinx,以进一步增强各自的数据中心业务,这也说明了异构计算对产业界的重要性。
并行通信(互连网络)
通信主要包括两个方面的因素,带宽和延时。带宽的提升相对容易,只要增加信道的容量,或者是增加并行的链路数量,就能增加整体的带宽。延时的改进则要困难很多,因为信号的传播不可能超过光速,它和距离一起决定了延时的下限。所以优化延时,就需要增加信号的传播速度和缩短传输距离。前者主要是改进传输的媒介和信道,以及避免信道拥塞,例如使用更高质量的光纤,使用更先进的编码算法。后者,则需要优化传输的路径,以直连代替中转。
所以,要提高通信效率,最根本的途径就是提升通信链路的速度、容量和密度。这跟现实社会中的交通运输网络是一样的,不管是公路、铁路、还是航空、水运,或者是运输管道,都是一方面尽力去提速,另一方面不断提升网络的密度,增加区域间的直连通道。在网络系统中,共享带宽的集线器被连接密度更高的交换机取代,还有数据中心里使用的CLOS架构,以较低的成本实现大量服务器之间的网络互连,也都是相同的道理。
传统的计算机系统在硬件层面是以CPU为核心的星型拓扑结构,通过系统总线来进行通信。这在CPU性能远超其它设备的情况下,是可以满足需求的。但现代的异构并行计算体系中,各个计算芯片(包括CPU和各种加速芯片)之间传输数据的需求非常高,传统的总线无法满足要求。所以就有了各种在芯片之间建立直连通道的通信网络和协议,使得芯片之间可以直接的和并行的进行数据传输,例如前面提到的QPI/NVLink/CXL等。
除了计算芯片之间的通信,还有CPU跟网卡、存储设备之间,也存在并行通信的需要。传统的网卡只有一个数据队列,在高速传输时CPU的多核不能发挥作用,现在的一些高速网卡中提供了多个数据队列,由不同的CPU核心来处理不同队列的中断请求,避免CPU称为网络传输的瓶颈,提高网络传输能力。传统的硬盘设备和接口只支持一个数据队列,而新的NVMe接口的SSD可以支持高达64K个I/O队列,相当于在CPU和外部存储设备之间建立更多的传输通道,可以并行的传输数据,提升I/O吞吐。
同样,在软件层面,计算机也可以认为是以操作系统为中心的星型结构,在高性能计算场景中,OS内核容易成为瓶颈。历史上Linux内核对并行的支持有限,早期使用粗粒度的锁,很多数据结构也是全局一份,后来才逐步提高并行度,例如使用粒度更细的锁,在线程调度、内存分配等场景中使用跟CPU核心关联的多份数据结构等。还有DPDK/SPDK等Kernel-Bypass技术,让用户态程序绕开系统内核直接访问I/O设备,也可以理解为软件层面的互连通信协作,避免OS内核介入后造成的瓶颈。