在本系列前面的文章中,我们先后介绍了:
单进程模型
多进程模型
多线程模型
IO 多路复用技术
每种模型都有自身的优缺点,都有自己的适用场景,比如单进程模型一般仅仅在开发测试中使用,多进程和多线程模型会用在并发请求不是很多的场景下,比如内部系统,数据库系统等,IO 多路复用技术则在很多高性能 web server 中发挥着重要作用,比如我们常见的 nginx。
那如何衡量一个 web server 是高性能的,又如何设计出高性能的 web server?
高性能 web server 的衡量指标
可量化是衡量性能的一种重要方式,对于高性能的 web server 可以从以下几个指标入手:
最大并发连接数
响应时间
吞吐量
吞吐量 = 最大并发连接数/响应时间,所以提高一个 web server 的性能就是不断提高这些指标,但是要提高这些指标有一个非常重要的前置条件,就是不能靠单纯提高硬件的性能来达到高性能的要求,而是在逼近硬件性能极限的条件下通过不断优化软件来实现高性能的 web server。
高性能的 web server 模型
从单进程模型到 IO 多路复用技术,每种模型都有其自身的特点:
单进程模型实现简单,适用场景非常有限
多进程模型有一定的并发能力,实现简单,但是数据共享很麻烦
多线程模型有一定的并发能力,数据共享比较简单,但是会引入锁机 制,实现稍微复杂,
IO 多路复用技术可以有效解决 IO 等待的问题,但是要利用多核优势还需要多进程或多线程。
所以现实中 web server 的实现都是这几种模型的综合应用,组合起来一般包括: 1.单线程 IO 多路复用技术 + 单进程(单进程模型) 2.单线程 IO 多路复用技术 + 多线程(单进程多线程) 3.多线程/多进程 IO 多路复用技术 + 多进程/多线程(多进程或多进程多线程模型) 4.多线程/多进程 + 异步 IO
不论是采用哪种模型详细处理流程大致如下:
不同模型的差异在于 accept 操作、business process、IO 操作是单个线程完成还是多个线程或多个进程完成。
单线程 IO 多路复用技术 + 单进程(单进程模型)
这种模型中,accept 操作、业务逻辑处理、IO 处理都是在一个进程中单线程下处理完成,典型如 tornado、redis。
单线程 IO 多路复用技术 + 多线程(单进程多线程)
这种模型中,accept 操作是单独的线程,IO以及业务逻辑处理按照不同场景需求分别交给单独的线程或同一个线程来处理,典型如 gunicorn 支持单进程多线程。
多线程/多进程 IO 多路复用技术 + 多进程/多线程
这种模型中,accept 操作是多个的线程或进程实现,IO以及业务逻辑处理也是由多个线程完成或进程完成,典型如 nginx 多进程,每个进程都有自己的 accept
多线程/多进程 + 异步 IO
linux 中真正的异步 IO 实现用的还比较少,在此我们不做深入介绍,感兴趣的读者可以看看 Windows 平台的异步 IO 实现 https://docs.microsoft.com/en-us/windows/desktop/fileio/i-o-completion-ports
高性能 web server 的实现原则
虽然现在开源实现中各种 web server 细节差异非常大,但总体来说所有的 web server 在实现高性能要求时都遵守类似的原则:
尽量避免数据拷贝(Zero Copy)
尽量避免上下文切换(Context Switchs)
尽量减少内存分配次数(Memory Allocation)
尽量减少锁的使用(Lock)
Zero Copy
没有数据拷贝的 web server 几乎是不可能实现的,代码复杂度高,而且很难经得起真实场景的考验,即使应用程序能够做到不拷贝数据,但是内核也需要从网络中拷贝数据。既然理想的状态难以达到,我们可以折中实现:尽量避免大对象的拷贝。
Context Switchs
在 linux 系统中,如果 web server 进程过多或线程过多都会造成系统在处理请求时需要频繁进行上下文切换,因而一个高性能 web server 的开启的进程数或线程数需要严格控制,一般不大于 CPU 的核心数。
Memory Allocation
大量而且频繁的内存分配是高性能的一个致命杀手,为了尽量避免在 web server 大量请求处理过程中内存的频繁分配,可以考虑使用:
预分配
对象的复用比如使用池化技术
避免使用锁
Lock
在多线程或多进程的 web server 中锁由于潜在的多线程竞争,锁的使用不可避免,因而我们只能尽量去控制在不需要锁的地方尽量不使用,在需要使用锁的地方控制好锁的粒度。
领取专属 10元无门槛券
私享最新 技术干货