众所周知,如今的服务器能够处理多个socket链接,背后并发的实现主要有两种途径。php
聊到socket,就不得不提到socket的创建的流程。祭出经典的老图:
node
服务器依次使用socket,bind,listen以后就会监听对应的地址,此时accept会一直阻塞直到有链接创建,若是客户端和服务器创建了链接,那么accept就会返回一个链接句柄,能够对链接进行读数据或者写数据。nginx
那么问题来了,服务器若是不作特殊处理的话,一次只能处理一个链接,新的链接来是须要等待上一个链接结束才能链接成功,这就是最开始的服务器同步阻塞方法。redis
同步阻塞:进程发起IO系统调用后,进程被阻塞,转到内核空间处理,整个IO处理完毕后返回进程。操做成功则进程获取到数据。segmentfault
可能之前的拥有的电脑人很少,这种方式一次只能链接一个倒也没有问题,以后访问的人开始多起来,设计者以为这样下去不行,就设计了多线程同步阻塞的方法。每次accept得到一个句柄,就建立一个线程去处理链接,这下子就能同时处理多个链接呢。这就是经典的多线程同步阻塞的方法。服务器
典型的多线程(进程)并发模型就是cgi。网络
服务器和客户端之间的并发,有如下特色:多线程
根据以上特色,咱们能够得知服务器有如下问题:
若是采用多线程同步阻塞,1个tcp链接须要创建1个线程,10k个链接须要创建10k个链接,然而大部分链接是不活跃,即使是须要处理业务逻辑,也能够快速返回结果,大部分时间也是处于I/O阻塞或网络等待。这就使得多个线程的建立很耗费资源,且线程的切换也是极其耗费CPU,这就极可能致使了CPU处理业务的消耗的资源很少,可是却花了不少资源在进程切换上面。并发
多线程并发的问题是大部分socket都是闲置的状态或者是处于IO阻塞的状态,那能不能把阻塞的socket先扔到一边去处理其余事情,来避免等待所带来的资源耗费,也就是非阻塞IO的概念。异步
当用户进程发出read操做时,若是kernel中的数据尚未准备好,那么它并不会block用户进程,而是马上返回一个error。从用户进程角度讲 ,它发起一个read操做后,并不须要等待,而是立刻就获得了一个结果。用户进程判断结果是一个error时,它就知道数据尚未准备好,因而它能够再次发送read操做。一旦kernel中的数据准备好了,而且又再次收到了用户进程的system call,那么它立刻就将数据拷贝到了用户内存,而后返回。
所以:使用非阻塞IO是须要不断轮询IO数据是否好了。
IO多路复用原理就是不断轮询多个socket,当其中的某个socket准备好了数据就返回,不然整个进程继续阻塞,就可让一个进程在不太耗费资源的状况下处理多个链接,可是这个轮询的操做是交给内核态去完成,也就避免了内核态和用户态的切换的问题。
而目前的实现方法有select, poll, epoll,其中epoll的性能最好,用的也是最普遍。
而epoll的实现能够作到性能几乎不受链接数(单单是链接而没有其余的操做)的影响。
固然多路复用IO也有本身的问题,也就是自己不支持多核的使用,须要另外解决多核的利用。
其中使用enroll的成熟程序有nginx,redis,nodej等。
根据知乎大佬的介绍,服务器通过发展能够分为两阶段:
把传输层的tcp并发的链接放到IO多路复用去处理,应用层继续使用多线程并发模型去作。这样就能够大幅度减小线程的建立切换的资源耗费。
如:nginx + php-fpm(实际上是php-fpm是多进程)
第二代服务器模型是把应用层也使用IO多路复用去处理,减小应用层的等待外部接口调用阻塞等待,通常是大厂大流量并发须要用到。
如:
参考资料:
[1]:许怀远的知乎回答
[2]:Linux IO模式及 select、poll、epoll详解