现代大型高性能网站诸如淘宝,京东,微博,FB,知乎等等,网站架构涉及不少知识。像业务分层,软件分割模块化,分布式部署,集群服务器,负载均衡等技术能够帮助架构师将一个大的复杂的问题切分红小的简单的问题。这篇文章着眼于解决这些切好的小问题上,单机上有哪些编程实践或者模型能够很好的作到高并发。本人web开发小白一枚,写文章是想梳理本身的思路,求得大牛斧正,但愿各位多多批判。文章的内容大多来自网上的阅读加上些本身的理解,文末附上参考阅读的文章。html
由于有数年的嵌入式领域的经验,先说一下我认为的比较高效的处理模型。linux
硬件环境:单机30core, 1G Hz。nginx
软件环境:6Wind fastpath,每一个core上都是run-to-complete的endless loop.没有操做系统。git
功能:一个超级简单的reverse proxy,具备load balance的简单功能。github
衡量并发性能,咱们看一下一个IP包从网口缓冲区收上来处理到发出去大约须要多长时间呢?web
图spring
收+处理+发大概是500+1000+500=2000 cycles,时间也就是2us。单机1s内能够支持30*(1s/2us)=15,000,000 request/s的并发。屌炸天的并发能力了吧!缘由有两个:apache
没有操做系统overhead。编程
包处理简单,IP层的处理,直接c函数调用,总共1000 cycle。服务器
固然这是从嵌入式得来的经验,web开发中不可能这样,没有Nginx,没有web框架,没有lib没有各类open source,甚至没有linux。回到原始社会造出飞机大炮来,这不把web开发者逼疯了。软件也是一个社会化协做的过程,os,framework,lib,opensource给开发者带来极大方便的同时,也伴随着性能的开销。如何在性能和可扩展性、维护性等其余指标找到一个平衡点,如何选择合适的编程模型,合适的第三方模块达到最小的overhead,这是成长为高手的开发者都会不断思考的问题。
High Performance architecture,这篇文章总结了四个性能杀手:
数据复制
上下文切换
动态内存分配
锁竞争
上面的编程模型之因此高效,就是将CPU用到极致,尽可能避免这4种状况发生。心中有这么一个极简的高效模型,后面学习其余模式的时候能够暗作对比看一下到底会有哪些额外的开销。
大名鼎鼎的Nginx使用了多进程模型,主进程启动时初始化,bind,监听一组sockets,而后fork一堆child processes(workers),workers共享socket descriptor。workers竞争accept_mutex,获胜的worker经过IO multiplex(select/poll/epoll/kqueue/...)来处理成千上万的并发请求。为了得到高性能,Nginx还大量使用了异步,事件驱动,non-blocking IO等技术。"What resulted is a modular, event-driven, asynchronous, single-threaded, non-blocking architecture which became the foundation of nginx code."
Nginx 架构
对比着看一下Apache的两种经常使用运行模式,详见 Apache Modules
1. Apache MPM prefork模式
主进程经过进程池维护必定数量(可配置)的worker进程,每一个worker进程负责一个connection。worker进程之间经过竞争mpm-accept mutex实现并发和连接处理隔离。 因为进程内存开销和切换开销,该模式相对来讲是比较低效的并发。
2. Apache MPM worker模式
因为进程开销较大,MPM worker模式作了改进,处理每一个connection的实体改成thread。主进程启动可配数量的子进程,每一个进程启动可配数量的server threads和listen thread。listen threads经过竞争mpm-accept mutex获取到新进的connection request经过queue传递给本身进程所在的server threads处理。因为调度的实体变成了开销较小的thread,worker模式相对prefork具备更好的并发性能。
小结两种webserver,能够发现Nginx使用了更高效的编程模型,worker进程通常跟CPU的core数量至关,每一个worker驻留在一个core上,合理编程能够作到最小程度的进程切换,并且内存的使用也比较经济,基本上没有浪费在进程状态的存储上。而Apache的模式是每一个connection对应一个进程/线程,进程/线程间的切换开销,大量进程/线程的内存开销,cache miss的几率增大,都限制了系统所能支持的并发数。
因为IO的处理速度要远远低于CPU的速度,运行在CPU上的程序不得不考虑IO在准备暑假的过程当中该干点什么,让出CPU给别人仍是本身去干点别的有意义的事情,这就涉及到了采用什么样的IO策略。通常IO策略的选用跟进程线程编程模型要同时考虑,二者是有联系的。
同步阻塞IO
同步阻塞IO是比较常见的IO模型,网络编程中若是建立的socket的描述符属性设置为阻塞的,当socket对应的用户空间缓冲区内尚无可读数据时,该进程/线程在系统调用read/recv socket时,会将本身挂起阻塞等待socket ready。
同步非阻塞IO和非阻塞IO同步复用
同步非阻塞IO
非阻塞IO同步复用
对比着同步阻塞IO,若是socket数据没有ready,系统调用read/recv会直接返回,进程能够继续执行不会挂起让出CPU。固然这样作对单个socket来讲没有多大的意义,若是要支持大量socket的并发就颇有用了,也就是IO复用。select/poll/epoll就是这样的应用,IO的read是非阻塞式调用,select是阻塞式的,同步发生在select上。程序经过select调用同时监控一组sockets,任何一个socket发生注册过的事件时,select由阻塞变为ready,函数调用返回后程序能够读取IO了。前面提到的Nginx(使用epoll)和apache(使用select)都有使用这一IO策略。select/epoll这种IO策略还有另一个名字叫Reactor,具体他们之间的细节区别再另开一文。
异步非阻塞IO
对比同步非阻塞IO,异步非阻塞IO也有个名字--Proactor。这种策略是真正的异步,使用注册callback/hook函数来实现异步。程序注册本身感兴趣的socket 事件时,同时将处理各类事件的handler也就是对应的函数也注册给内核,不会有任何阻塞式调用。事件发生后内核之间调用对应的handler完成处理。这里暂且理解为内核作了event的调度和handler调用,具体究竟是异步IO库如何作的,如何跟内核通讯的,后续继续研究。
High Performance architecture
Threads vs. processes for program parallelization
WebServerArchitectures
Concurrent Programming for Scalable Web Architectures
Apache Architecture
Apache Modules
Ngnix Architecture
epoll编程,如何实现高并发服务器开发