执行完以上步骤,一个服务器正式开始服务。下面我们看一下基于上面的模型,分析各种各样的处理方法。
1 单进程accept
上面是一个服务器处理连接最朴素的模型,处理逻辑就是服务器不断地调用accept摘下完成三次握手的连接,然后处理,如果没有连接则服务器阻塞。我们看看这种模式的处理过程。假设有n个请求到来。那么socket的结构是。
言归正传,串行这种模式如果处理的过程中有调用了阻塞api,比如文件io,就会影响后面请求的处理。可想而知,效率是有多低。而且并发量比较大的时候,监听socket对应的队列很快就会被占满(已完成连接队列有一个最大长度)。这是最简单的模式,虽然服务器的设计中肯定不会使用这种模式,但是他让我们了解了一个服务器处理请求的整体过程。
2 多进程模式
串行模式中,所有请求都在一个进程中排队被处理,这是效率低下的原因。这时候我们可以把请求分给多个进程处理来提供效率,因为在串行处理的模式中,如果有文件io操作,他就会阻塞主进程,从而阻塞后续请求的处理。在多进程的模式中,即使一个请求阻塞了进程,那么操作系统会挂起该进程,接着调度其他进程执行,那么其他进程就可以执行新的任务。多进程模式下分为几种。
2.1 主进程accept,子进程处理请求
这种模式下,主进程负责摘取已完成连接的节点,然后把这个节点对应的请求交给子进程处理,逻辑如下。
这种模式下,每次来一个请求,就会新建一个进程去处理。这种模式比串行的稍微好了一点,每个请求独立处理,假设a请求阻塞在文件io,那么不会影响b请求的处理,尽可能地做到了并发。他的瓶颈就是系统的进程数有限,如果有大量的请求,系统无法扛得住。再者,进程的开销很大。对于系统来说是一个沉重的负担。
2.2 子进程accept
这种模式不是等到请求来的时候再创建进程。而是在服务器启动的时候,就会创建多个进程。然后多个进程分别调用accept。这种模式的架构如下。
这种模式下多个子进程都阻塞在accept。如果这时候有一个请求到来,那么所有的子进程都会被唤醒,但是首先被调度的子进程会首先摘下这个请求节点。后续的进程被唤醒后可能会遇到已经没有请求可以处理。又进入睡眠,进程被无效唤醒,这是著名的惊群现象。架构如下。
使用进程池的模式时,主进程负责accept,然后把请求交给子进程处理,但是和多进程的模式2.1相比,进程池模式相对比较复杂,因为在多进程模式2.1中,当主进程收到一个请求的时候,实时fork一个子进程,这时候,这个子进程会继承主进程中新请求对应的fd,所以他可以直接处理该fd对应的请求,在进程池的模式中,子进程是预先创建的,当主进程收到一个请求的时候,子进程中是无法拿得到该请求对应的fd的。这时候,需要主进程使用传递文件描述符的技术把这个请求对应的fd传给子进程。一个进程其实就是一个结构体task_struct,他有一个字段记录了打开的文件描述符,当我们访问一个文件描述符的时候,操作系统就会根据fd的值,从task_struct中找到fd对应的底层资源,所以主进程给子进程传递文件描述符的时候,传递的不仅仅是一个数字fd,因为如果仅仅这样做,在子进程中该fd可能没有对应任何资源,或者对应的资源和主进程中的是不一致的。而传递文件描述符,操作系统帮我们处理了很多事情,让我们在子进程中可以通过fd访问到正确的资源,即主进程中收到的请求。
3 多线程模式
多线程模式和多进程模式是类似的,也是分为下面几种
1 主进程accept,创建子线程处理
2 子线程accept
3 线程池
前面两种和多进程模式中是一样的,但是第三种比较特别,我们主要介绍第三种。在子进程模式时,每个子进程都有自己的task_struct,这就意味着在fork之后,每个进程负责维护自己的数据,而线程则不一样,线程是共享主线程(主进程)的数据的,当主进程从accept中拿到一个fd的时候,传给线程的话,线程是可以直接操作的。所以在线程池模式时,架构如下。
现在的服务器的设计中还会涉及到协程。不过目前自己还没有看过具体的实现,所以还无法介绍(想了解原理的话可以看libtask这个协程库)。
5 reuseport端口复用
前面介绍的几种模式中,在处理连接的方案上,大致有下面几种
1 单进程串行处理
2 主进程接收连接,分发给子进程处理。
3 子进程接收请求,有惊群现象。
从串行处理到多进程/多线程模式,在处理连接上有了很大的改进,但是依然存在一些问题,2中的问题是,虽然有多个子进程处理请求,但是只有一个进程接收请求,这是远远不够的。3中的问题是,多个子进程可以同时accept,首先会导致惊群问题,其次,被唤醒处理连接的进程应该处理多少个连接也是一个问题,比如有10个连接,进程1被唤醒后是全部处理还是只处理一个,把剩下的留给其他进程处理呢使新版的内核已经解决了惊群问题,但是被唤醒的进程应该处理多少个连接的问题依然存在,所以如何接收请求和分发请求是两个可以改进的地方,新版linux支持reuseport特性后,使得处理请求的模式有了很大的改善。reuseport之前,一个socket是无法绑定到同一个地址的,通常的做法是主进程bind后,fork子进程,然后子进程listen。但是共享的是同一个socket。reuseport特性支持多个socket绑定到同一个地址,当连接到来时,操作系统会根据地址信息找到一组socket,然后根据策略选择一个socket,然后唤醒阻塞在该socket的进程。这样之前多进程共享socket的模式下,被唤醒的进程应该处理多少个请求的问题也解决了,因为reuseport模式中,每个进程一个socket,对应一个请求队列,内核会把请求分发到各个socket中,被socket唤醒的进程只处理自己的监听socket下的连接就行,架构如下

这种模式在底层解决了多进程请求分发的问题,提高了处理请求的效率同时实现了负载均衡。
以上是服务器处理请求的架构演变,服务器作为对性能要求极高的软件,在技术演变的过程中,不仅应用层做了很多改进,操作系统内核层面也做了很多改进。
声明:本站部分文章及图片源自用户投稿,如本站任何资料有侵权请您尽早请联系jinwei@zod.com.cn进行处理,非常感谢!