目录
以高性能著称的服务器,不论是专用的流媒体服务器(如HelixServer),还是通用的HTTP服务器(如Nginx),其在当前的计算机体系架构下,采用的思路大体一致,例如采用异步的网络通信方式以避免连接增加对性能带来的线性损害,设计完全基于回调函数的进程或线程模型避免频繁的线程上下文切换,自我管理内存分配避免陷入内核的耗时,对锁机制的细致使用最大概率避免竞争状态等,以下将分别进行详细介绍。
异步模式的网络编程,或更准确地说,基于多路复用模式的异步网络编程,是提升服务器性能的重要原理之一,早先的Apache服务器虽然简单易用,但当同时连接的用户达到千级或更多时就不堪重负。故而在不同操作系统上普遍发展出了高性能多路复用的技术,如BSD上的Kqueue,Windows上的完成端口,Linux上的Epoll,Solaris上的/dev/poll等。


2、上下文切换
同样对服务器性能影响极大的,是线程或进程的频繁切换,对一个H.264的NAL进行打包,或者一次不需要陷入内核的glibc调用,可能只需要几个CPU tick,但线程或进程交出运行权,换出内存、保存线程栈等操作则可能耗费高达数百甚至上千tick的时间,因此设计适合当前多核CPU架构的进程、线程模型十分重要。
首先,我们希望进程或线程在某个CPU核心上尽可能连续运行,服务器应根据不同的操作系统选取进程或线程作为基本运行单位,开启适合的进程、线程数量,如与CPU核数相同。
其次,对所有的处理操作都按照事件驱动的模型进行编写,避免任何阻塞的硬盘读写以及网络读写操作,以令一个进程或线程可以持续地处理成百甚至上千个链接。在此模型下,针对一个网络链接的处理,原则上只由一个进程或线程完成,避免数据的传递以及不同进程或线程间的相互依赖。
此外,服务器还可以将进程或线程绑定CPU的某个核,以避免竞争状态。
为追求高性能,现代的服务器往往不依赖于操作系统,而是自己管理内存,由于流媒体服务器需要处理的数据量远较一般的通用HTTP服务器要大,对内存进行自己的管理就显得更有效果。
其次,针对每次内存分配尽可能在本线程所据有的内存中完成,避免需要加锁保护的情况。
对于常用的内存块大小,提前进行预测分配。
下面是Helix Server中不同层次内存的管理机制:
4、锁机制
系统中如果使用任何锁机制,不论是操作系统提供,等待满足条件后唤醒的内核态锁,还是在“忙等”的用户态锁,都是对运行效率的一种降低。
根据进程、线程模式的设计,绝大部分情况下,各个进程或线程都应该独自处理用户请求,在同一上下文内并无加锁的必要,但仍然有一些关于数据交换或共享的需求,必须加锁来完成,这时就需要详细考量。
首先,形如pthread_mutex_lock()因为可能陷入内核,应该尽量避免使用,其次,在各个系统中均常见的Atomic锁,利用了CPU提供的锁总线功能完成,仅花费一个CPU tick,效率较高,适合保护极细粒度的内容,例如数据块的引用计数,在使用时将锁住所有进程或线程。
另一种适合的锁是Spinlock,其原理是让CPU进行一些循环,持续检测标志位的状态变化,再用锁总线的方式更改标志位。
在流媒体服务器中,常见以下需要锁的示例。
1.需要读写全局配置项,此时可考虑类似Windows注册表一样的做法,对每一个注册表项都使用一个独立的Spinlock,互不干扰,降低访问冲突。
2.需要在不同进程或线程间传递数据,如Socket,改变数据的持有者,可以依据进程、线程以及数据类型建立不同的访问队列,然后针对不同队列的访问加不同的锁,目的同样是为了降低访问冲突。
3.不同进程、线程的访问请求指向同一份音视频内容,可以针对每份内容在不同进程、线程中复制相同的对象,对对象本身的访问加锁,以及对该对象管理下的所有Packet分别加锁(引用计数)。
鉴于流媒体服务对I/O的高要求,另一项需要认真对待的原则是尽量实现零内存拷贝,一次常见的读写操作常常会触发四次内存拷贝。内存拷贝的高带宽、高速度针对其他服务或许并非掣肘,但对流媒体服务而言,单台服务器的吞吐量常常可高达Gbit/s级别,高者达到数十Gbit/s,内存拷贝的代价并非可以忽略。
1.Direct I/O,允许用户的应用程序直接访问硬件,但缺少内存页的缓存。
3.sendfile(),类似于mmap,主要避免了从内核中向Socket缓冲区的拷贝。
5.fbufs,可以允许在应用和内核间分配共享的fbufs。
6、其他实践
为解决并发和I/O问题,还有其他一些思路。例如运用FPGA在内核态进行线速的解包、封包和转发,同样可以达到很高的并发。
当前集中式、设计复杂的专用硬件虽不再流行,亦有一些公司在尝试基于廉价x86配合FPGA或DSP芯片,重载系统的sendfile()接口,达到高吞吐量。
近年来,随着用户需求的增长,资源密度的增大,使用单Linux或FreeBSD服务器提供100Gbit/s的流媒体吞吐甚至TLS访问能力也提上了日程,对此,Netflix在2017年的一篇Tech Blog中详述了遇到的问题和解决方式,包括:
1.在FreeBSD上对非活动页面队列进行缓存,避免锁的消耗;
2.增加pbufs启动分配数量和更改vnode pager;
3.主动扫描VM页面队列以避免Pageout进程的Burst;
4.实现批量排序的TCP LRO(Large Receive Offload)算法;
5.使用Intel的VTune工具分析内存带宽占用;
6.更新ISA-L加密库,使用DDR4-2400内存取代DDR4-1866;
7.修改内核数据结构以提升缓存命中;
8.修改TCP相关计数,提升由此导致的缓存丢失和CPU消耗;
9.修改FreeBSD网络栈中的mbuf类型,检查NIC的驱动,以保证其不使用mtod()方法访问。
No comments yet.