WEB服务访问的过程
先说说线程、进程、以及并发连接数,在说Web服务器。
1.进程与线程
进程是具有⼀定独⽴功能的程序,关于某个数据集合上的⼀次运⾏活动,进程是系统进⾏资源分配和调度的⼀个独⽴单位。从逻辑⾓度来看,多线程的意义在于⼀个应⽤程序(进程)中,有多个执⾏部分可以同时执⾏。但操作系统并没有将多个线程看做多个独⽴的应⽤来实现,⽽是作为进程来调度和管理以及资源分配。这就是进程和线程的重要区别,进程和线程的主要差别在于,进程有独⽴的地址空间,⼀个进程崩溃后,在保护模式下不会对其它进程产⽣影响,⽽线程只是⼀个进程中的不同执⾏路径。线程有⾃⼰的堆栈和局部变量,但线程之间没有单独的地址空间,⼀个线程死掉就等于整个进程死掉,所以多进程的程序要⽐多线程的程序健壮,但在进程切换时,耗费资源较⼤,效率要差⼀些。但对于⼀些要求同时进⾏并且⼜要共享某些变量的并发操作,只能⽤线程,不能⽤进程。下⾯我们来说⼀说并发连接数。
2.并发连接数
(1).什么是最⼤并发连接数呢?
最⼤并发连接数是服务器同⼀时间能处理最⼤会话数量。
(2).何为会话?
我们打开⼀个⽹站就是⼀个客户端浏览器与服务端的⼀个会话,⽽我们浏览⽹页是基于http协议。
(3).HTTP协议如何⼯作?
HTTP⽀持两种建⽴连接的⽅式:⾮持久连接和持久连接(HTTP1.1默认的连接⽅式为持久连接)。
(4).浏览器与Web服务器之间将完成下列7个步骤
建⽴TCP连接
Web浏览器向Web服务器发送请求命令
Web浏览器发送请求头信息
Web服务器应答
Web服务器发送应答头信息
Web服务器向浏览器发送数据
Web服务器关闭TCP连接
⼀般情况下,⼀旦Web服务器向浏览器发送了请求数据,它就要关闭TCP连接,但是浏览器⼀般其头信息加⼊了这⾏代码 Connection:keep-alive,TCP连接在发送后将仍然保持打开状态,于是,浏览器可以继续通过相同的连接发送请求。保持连接⽬的,节省了为每 个请求建⽴新连接所需的时间,还节约了⽹络带宽。
3.并发连接数的计算⽅法
⽤户下载服务器上的⽂件,则为⼀个连接,⽤户⽂件下载完毕后这个连接就消失了。有时候⽤户⽤迅雷的多线程⽅式下载的话,这⼀个⽤户开启了5个线程的话,就算是5个连接。        ⽤户打开你的页⾯,就算停留在页⾯没有对服务器发出任何请求,那么在⽤户打开⼀⾯以后的15分钟内也都要算⼀个在线。
上⾯的情况⽤户继续打开同⼀个⽹站的其他页⾯,那么在线⼈数按照⽤户最后⼀次点击(发出请求)以后的15分钟计算,在这个15分钟内不管⽤户怎么点击(包括新窗⼝打开)都还是⼀⼈在线。
当⽤户打开页⾯然后正常关闭浏览器,⽤户的在线⼈数也会马上清除。
⼆、Web服务器提供服务的⽅式
Web服务器由于要同时为多个客户提供服务,就必须使⽤某种⽅式来⽀持这种多任务的服务⽅式。⼀般情况下可以有以下三种⽅式来选择,多进程⽅式、多线程⽅式及异步⽅式。其中,多进程⽅式中服务器对⼀个客户要使⽤⼀个进程来提供服务,由于在操作系统中,⽣成⼀个进程需要进程内存复制等额外的开销,这样在客户较多时的性能就会降低。为了克服这种⽣成进程的额外开销,可以使⽤多线程⽅式或异步⽅式。在多线程⽅式中,使⽤进程中的多个线程提供服务, 由于线程的开销较⼩,性能就会提⾼。事实上,不需要任何额外开销的⽅式还是异步⽅式,它使⽤⾮阻塞的⽅式与每个客户通信,服务器使⽤⼀个进程进⾏轮询就⾏了。
虽然异步⽅式最为⾼效,但它也有⾃⼰的缺点。因为异步⽅式下,多个任务之间的调度是由服务器程序⾃⾝来完成的,⽽且⼀旦⼀个地⽅出现问题则整个服务器就会出现问题。因此,向这种服务器增加功能,⼀⽅⾯要遵从该服务器⾃⾝特定的任务调度⽅式,另⼀⽅⾯要确保代码中没有错误存在,这就限制了服务器的功能,使得异步⽅式的Web服务器的效率最⾼,但功能简单,如Nginx服务器。
由于多线程⽅式使⽤线程进⾏任务调度,这样服务器的开发由于遵从标准,从⽽变得简单并有利于多⼈协作。然⽽多个线程位于同⼀个进程内,可以访问同样的内存空间,因此存在线程之间的影响,并且申请的内存必须确保申请和释放。对于服务器系统来讲,由于它要数天、数⽉甚⾄数年连续不停的
运转,⼀点点错误就会逐渐积累⽽最终导致影响服务器的正常运转,因此很难编写⼀个⾼稳定性的多线程服务器程序。但是,不是不能做到时。Apache的worker模块就能很好的⽀持多线程的⽅式。
多进程⽅式的优势就在于稳定性,因为⼀个进程退出的时候,操作系统会回收其占⽤的资源,从⽽使它不会留下任何垃圾。即便程序中出现错误,由于进程是相互隔离的,那么这个错误不会积累起来,⽽是随着这个进程的退出⽽得到清除。Apache的prefork模块就是⽀持多进程的模块。
三、多进程、多线程、异步模式的对⽐
Web服务器总的来说提供服务的⽅式有三种,多进程⽅式,多线程的⽅式,异步⽅式。其中效率最⾼的是异步的⽅式,最稳定的是多进程⽅式,占⽤资源较少的是多线程的⽅式。
1.多进程
此种架构⽅式中,web服务器⽣成多个进程并⾏处理多个⽤户请求,进程可以按需或事先⽣成。有的web服务器应⽤程序为每个⽤户请求⽣成⼀个单独的进程来进⾏响应,不过,⼀旦并发请求数量达到成千上万时,多个同时运⾏的进程将会消耗⼤量的系统资源。(即每个进程只能响应⼀个请求或多个进程对应多个请求)
优点:
最⼤的优势就在于稳定性,⼀个进程出错不会影响其它进程。如,服务器同时连接100个请求对就的是100个进程,其中⼀个进程出错,只会杀死⼀个进程,还有99个进程继续响应⽤户请求。每个进程响应⼀个请求
缺点:
进程量⼤,进程切换次数过多,导致CPU资源使⽤效率低,每个进程的地址空间是独⽴的,很多空间中重复的数据,所以内存使⽤效率低,进程切换由于内核完成,占⽤CPU资源。
2.多线程
在多线程⽅式中,每个线程来响应⼀下请求,由于线程之间共享进程的数据,所以线程的开销较⼩,性能就会提⾼。
优点:
线程间共享进程数据,每个线程响应⼀个请求,线程切换不可避免(切换量级⽐较轻量),同⼀进程的线程可以共享进程的诸多资源,对内存的需求较之进程有很⼤下降,读可以共享,写不可以共享
缺点:web浏览器在哪里打开
线程快速切换时会带来线程抖动,多线程会导致服务器不稳定
3.异步⽅式
⼀个进程或线程响应多个请求,不需要任何额外开销的,性能最⾼,占⽤资源最少。但也有问题⼀但进程或线程出错就会导致整个服务器的宕机。
四、Web 服务请求过程
在上⾯的讲解中我们说明,Web服务器的如何提供服务的,有多进程的⽅式、多线程的⽅式还有异步⽅式我们先简单这么理解,后⾯我们慢慢说,现在我们不管Web服务器是如何提供服务的,多进程也好、多线程好,异步也罢。下⾯我们来说⼀下,⼀个客户端的具体请求Web服务的具体过程,从上图中我们可以看到有11步,下⾯我们来具体说⼀下,
1.⾸先我们客户端发送⼀个请求到Web服务器,请求⾸先是到⽹卡。
2.⽹卡将请求交由内核空间的内核处理,其实就是拆包了,发现请求的是80端⼝。
3.内核便将请求发给了在⽤
户空间的Web服务器,Web服务器接受到请求发现客户端请求的index.html页⾯。4.Web服务器便进⾏系统调⽤将请求发给内核。5.内核发现在请求的是⼀页⾯,便调⽤磁盘的驱动程序,连接磁盘。6.内核通过驱动调⽤磁盘取得的页⾯⽂件。7.内核将取得的页⾯⽂件保存在⾃⼰的缓存区域中便通知Web进程或线程来取相应的页⾯⽂件。8.Web服务器通过系统调⽤将内核缓存中的页⾯⽂件复制到进程缓存区域中。9.Web服务器取得页⾯⽂件来响应⽤户,再次通过系统调⽤将页⾯⽂件发给内核。10.内核进程页⾯⽂件的封装并通过⽹卡发送出去。11.当报⽂到达⽹卡时通过⽹络响应给客户端
简单来说就是:⽤户请求-->送达到⽤户空间-->系统调⽤-->内核空间-->内核到磁盘上读取⽹页资源->返回到⽤户空间->响应给⽤户。上述简单的说明了⼀下,客户端向Web服务请求过程,在这个过程中,有两个I/O过程,⼀个就是客户端请求的⽹络I/O,另⼀个就是Web服务器请求页⾯的磁盘I/O。 下⾯我们就来说说Linux的I/O模型。
五、Linux I/O 模型
1.I/O模型分类
说明:我们都知道web服务器的进程响应⽤户请求,但⽆法直接操作I/O设备,其必须通过系统调⽤,请求kernel来协助完成I/O动作,如下图:
对于数据输⼊⽽⾔,即等待(wait)数据输⼊⾄buffer需要时间,⽽从buffer复制(copy)数据⾄进程也需要时间
根据等待模式不同,I/O动作可分为五种模式。
1.阻塞I/O,
2.⾮阻塞I/O,
3.I/O复⽤(select和poll),
4.信号(事件)驱动I/O(SIGIO),
5.异步I/O(Posix.1的aio_系列函数)
2.I/O模型的相关术语
(1).阻塞和⾮阻塞:
阻塞和⾮阻塞指的是执⾏⼀个操作是等操作结束再返回,还是马上返回。⽐如你去车站接朋友,这是⼀个操作。可以有两种执⾏⽅式。第⼀种,你这⼈特实诚,⽼早就到了车站⼀直等到车来了接到朋友
为⽌。第⼆种,你到了车站,问值班的那趟车来了没有,“还没有”,你出去逛⼀圈,可能过会回来再问。第⼀种就是阻塞⽅式,第⼆种则是⾮阻塞的。我认为阻塞和⾮阻塞讲得是做事⽅法,是针对做事的⼈⽽⾔的。
(2).同步和异步:
同步和异步⼜是另外⼀个概念,它是事件本⾝的⼀个属性。⽐如⽼板让你去搬⼀堆⽯头,⽽且只让你⼀个⼈⼲,你只好⾃⼰上阵,最后的结果是搬完了,还是你砸到脚了,只有搬完了你才知道。这就是同步的事件。如果⽼板还给你个⼩弟,你就可以让⼩弟去搬,搬完了告你⼀声。这就变成异步的了。其实异步还可以分为两种:带通知的和不带通知的。前⾯说的那种属于带通知的。有些⼩弟⼲活可能主动性不是很够,不会主动通知你,你就需要时不时的去关注⼀下状态。这种就是不带通知的异步。 对于同步的事件,你只能以阻塞的⽅式去做。⽽对于异步的事件,阻塞和⾮阻塞都是可以的。⾮阻塞⼜有两种⽅式:主动查询和被动接收消息。被动不意味着⼀定不好,在这⾥它恰恰是效率更⾼的,因为在主动查询⾥绝⼤部分的查询是在做⽆⽤功。对于带通知的异步事件,两者皆可。⽽对于不带通知的,则只能⽤主动查询。
(3).I/O
回到I/O,不管是I还是O,对外设(磁盘)的访问都可以分成请求和执⾏两个阶段。请求就是看外设的状
态信息(⽐如是否准备好了),执⾏才是真正的I/O操作。在Linux 2.6之前,只有“请求”是异步事件,2.6之后才引⼊AIO把“执⾏”异步化。别看Linux/Unix是⽤来做服务器的,这点上⽐Windows落后了好多,IOC(Windows上的AIO)在Win2000上就有了。
(4).总结
Linux上的前四种I/O模型的“执⾏”阶段都是同步的,只有最后⼀种才做到了真正的全异步。第⼀种阻塞式是最原始的⽅法,也是最累的办法。当然累与不累要看针对谁。应⽤程序是和内核打交道的。对应⽤程序来说,这种⽅式是最累的,但对内核来说这种⽅式恰恰是最省事的。还拿接⼈这事为例,你就是应⽤程序,值班员就是内核,如果你去了⼀直等着,值班员就省事了。当然现在计算机的设计,包括操作系统,越来越为终端⽤户考虑了,为了让⽤户满意,内核慢慢的承担起越来越多的⼯作,IO模型的演化也是如此。⾮阻塞I/O ,I/O复⽤,信号驱动式I/O其实都是⾮阻塞的,当然是针对“请求”这个阶段。⾮阻塞式是主动查询外设状态。I/O复⽤⾥的select,poll也是主动查询,不同的是select和poll可以同时查询多个
fd(⽂件句柄)的状态,另外select有fd个数的限制。epoll是基于回调函数的。信号驱动式I/O则是基于信号消息的。这两个应该可以归到“被动接收消息”那⼀类中。最后就是伟⼤的AIO的出现,内核把什么事都⼲了,对上层应⽤实现了全异步,性能最好,当然复杂度也最⾼。
六、Linux I/O 模型具体说明
⾸先我们先来看⼀下,基本 Linux I/O 模型的简单矩阵,从图中我们可以看到的模型有,同步阻塞I/O(阻塞I/O)、同步⾮阻塞I/O(⾮阻塞I/O )、异步阻塞I/O(I/O复⽤),异步⾮阻塞I/O(有两种,信号驱动I/O和异步I/O)。好了现在就来具体说⼀说吧。
1.阻塞I/O
说明:应⽤程序调⽤⼀个IO函数,导致应⽤程序阻塞,等待数据准备好。 如果数据没有准备好,⼀直等待数据准备好了,从内核拷贝到⽤户空间,IO函数返回成功指⽰。这个不⽤多解释吧,阻塞套接字。下图是它调⽤过程的图⽰:(注,⼀般⽹络I/O都是阻塞I/O,客户端发出请求,Web服务器进程响应,在进程没有返回页⾯之前,这个请求会处于⼀直等待状态)
2.⾮阻塞I/O
我们把⼀个套接⼝设置为⾮阻塞就是告诉内核,当所请求的I/O操作⽆法完成时,不要将进程睡眠,⽽是返回⼀个错误。这样我们的I/O操作函数将不断的测试数据是否已经准备好,如果没有准备好,继续测试,直到数据准备好为⽌。在这个不断测试的过程中,会⼤量的占⽤CPU的时间,所有⼀般Web服务器都不使⽤这种I/O模型。具体过程如下图:
3.I/O复⽤(select和poll)
I/O复⽤模型会⽤到select或poll函数或epoll函数(Linux2.6以后的内核开始⽀持),这两个函数也会使进程阻塞,但是和阻塞I/O所不同的的,这两个函数可以同时阻塞多个I/O操作。⽽且可以同时对多个读操作,多个写操作的I/O函数进⾏检测,直到有数据可读或可写时,才真正调⽤I/O操作函数。具体过程如下图:
4.信号驱动I/O(SIGIO)
⾸先,我们允许套接⼝进⾏信号驱动I/O,并安装⼀个信号处理函数,进程继续运⾏并不阻塞。当数据准备好时,进程会收到⼀个SIGIO信号,可以在信号处理函数中调⽤I/O操作函数处理数据。具体过程如下图:
综上可以看出,越往后,阻塞越少,理论上效率也是最优。其五种I/O模型中,前三种属于同步I/O,后两者属于异步I/O。
同步I/O:
1.阻塞I/O,
2.⾮阻塞I/O,
3.I/O复⽤(select和poll)
异步I/O:
1.信号驱动I/O(SIGIO) (半异步),
2.异步I/O(Posix.1的aio_系列函数) (真正的异步)
异步 I/O 和 信号驱动I/O的区别:
信号驱动 I/O 模式下,内核可以复制的时候通知给我们的应⽤程序发送SIGIO 消息。异步 I/O 模式下,内核在所有的操作都已经被内核操作结束之后才会通知我们的应⽤程序。
七、Linux I/O模型的具体实现
1.主要实现⽅式有以下⼏种:
select,poll,epoll,kqueue,/dev/poll,iocp
其中iocp是Windows实现的,select、poll、epoll是Linux实现的,kqueue是FreeBSD实现的,/dev/poll是SUN的Solaris实现的。select、poll对应第3种(I/O复⽤)模
型,iocp对应第5种(异步I/O)模型,那么epoll、kqueue、/dev/poll呢?其实也同select属于同⼀种模型,只是更⾼级⼀些,可以看作有了第4种(信号驱动I/O)模型的某些特性,如callback机制。
2.为什么epoll、kqueue、/dev/poll⽐select⾼级?
答案是,他们⽆轮询。因为他们⽤callback取代了。想想看,当套接字⽐较多的时候,每次select()都要通过遍历FD_SETSIZE个Socket来完成调度,不管哪个Socket是活跃的,都遍历⼀遍。这会浪费很多CPU时间。如果能给套接字注册某个回调函数,当他们活跃时,⾃动完成相关操作,那就避免了轮询,这正是epoll、kqueue、/dev/poll做的。这样⼦说可能不好理解,那么我说⼀个现实中的例⼦,假设你在⼤学读书,住的宿舍楼有很多间房间,你的朋友要来你。select版宿管⼤妈就会带着你的朋友挨个房间去,直到到你为⽌。⽽epoll版宿管⼤妈会先记下每位同学的房间号,你的朋友来时,只需告诉你的朋友你住在哪个房间即可,不⽤亲⾃带着你的朋友满⼤楼⼈。如果来了10000个⼈,都要⾃⼰住这栋楼的同学时,select版和epoll版宿管⼤妈,谁的效率更⾼,不⾔⾃明。同理,在⾼并发服务器中,轮询I/O是最耗时间的操作之⼀,select、epoll、/dev/poll的性能谁的性能更⾼,同样⼗分明了。
3.Windows or *nix (IOCP or kqueue、epoll、/dev/poll)?
诚然,Windows的IOCP⾮常出⾊,⽬前很少有⽀持asynchronous I/O的系统,但是由于其系统本⾝的
局限性,⼤型服务器还是在UNIX下。⽽且正如上⾯所述,kqueue、epoll、/dev/poll 与 IOCP相⽐,就是多了⼀层从内核copy数据到应⽤层的阻塞,从⽽不能算作asynchronous I/O类。但是,这层⼩⼩的阻塞⽆⾜轻重,kqueue、epoll、/dev/poll 已经做得很优秀了。
4.总结⼀些重点
只有IOCP(windows实现)是asynchronous I/O,其他机制或多或少都会有⼀点阻塞。select(Linux实现)低效是因为每次它都需要轮询。但低效也是相对的,视情况⽽定,也可通过良好的设计改善epoll(Linux实现)、kqueue(FreeBSD实现)、/dev/poll(Solaris实现)是Reacor模式,IOCP是Proactor模式。Apache 2.2.9之前只⽀持select模型,2.2.9之后⽀持epoll模型,Nginx ⽀持epoll模型,Java nio包是select模型

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。