httpd之三种mpm工作模型

httpd的三种工作模型介绍

3种MPM工作模型

mpm:multi-processing module;多路处理模块,httpd共有3种mpm工作模式,分别是prefork,worker,event;单就处理请求的能力来说,是依次增大的;

httpd处理一次http请求流程

  1. 客户端向目标服务器发起建立连接请求
  2. 服务端在监听套接字接收到请求后,为其创建一个连接套接字,即和客户端的套接字建立一个连接
  3. 客户端发起具体方法的请求,如get 某资源
  4. 服务端接收后,识别其请求是什么
  5. 处理请求,如读取磁盘的文件,封装响应报文
  6. 发出去,客户端接收
  7. 服务端记录一条访问日志;

prefork

​ prefork是httpd一开始的工作模型;一个httpd主进程,fork出多个httpd工作进程,httpd启动时,根据startservers启动对应数量的子进程,默认是5,minspareservers默认是5,maxspareservers默认是10,空闲子进程在这2者之间调节,由主进程进行按需创建或杀掉!

​ 子进程负责轮流,监听,监听套接字,然后收到请求去处理;子进程能监听80,监听可以,bind低于1000的端口需要root权限

​ 以进程方式处理,更安全,但也更重量;进程间切换消耗的cpu是无生产力的,不比线程间切换;进程间内存空间独立,稳定,但数据共享不了,多个子进程请求同一个文件时,数据会在每个子进程空间都存一份,浪费了;

​ 进程:子进程监听请求,处理请求,一条龙服务到底

看到默认是5个子进程;
           ├─httpd(827)─┬─httpd(844)
           │            ├─httpd(845)
           │            ├─httpd(846)
           │            ├─httpd(847)
           │            └─httpd(848)

worker

​ worker相对prefork,仍是一个主进程创建多个子进程,但每一个子进程里有多个线程,N个线程处理请求,1个监听线程,负责监听请求,没有端口复用技术时,这些监听子线程通过争抢锁,来确定谁来监听那一个监听套接字文件

​ 相比worker,1、线程更轻量;2、同个进程内部,多个线程请求同一个文件可共享数据,内存利用率高;3、线程切换是在一个cpu时间片,cpu线程上下文切换比进程上下文切换,轻量的多;

​ 监听线程、工作线程,实现了接入请求、处理请求的分工,更高效;但,一个连接接入之后,仍是一个工作线程对其负责到底;长连接情况下,该连接很久没发请求,工作线程也得陪着它,不能接待其他的请求;效率较低,于是有了下面的event模型;

event

​ event采用:基于epoll的,事件驱动IO复用模型;

​ event相比worker最大的改进,就是采用异步连接,即一个连接由监听线程监听,接入之后,分配给一个工作线程处理它的请求,但是改连接没有请求发来时,工作线程也不陪它干耗着,而是将该连接套接字交给监听线程代为管理;自己又是空闲线程了,又可以被监听线程分配干活了,真勤劳,,监听线程代为管理暂时空闲下来的套接字,但套接字可以采用事件通知的机制,当有事件发生,比如我这个连接套接字又发来请求了,会报告给监听线程,监听线程会再为其分配一个工作线程进行处理;

​ 以下状态的连接套接字会被委托管理:

  • 空闲的长连接
  • closing状态的
  • 写等待的:即要写入send buffer时,send buffer满了,返回EWOULDBACK错误;

相关参数

  • Serverlimit:最大能启动多少个进程,硬限制;
  • threadlimt:最多能启动多个个线程,硬限制
  • startservers:默认启动多少个子进程,prefork默认5
  • minspareservers:maxspareservers:最小/最大的空闲进程数,超过最大就杀,小于最小就新启动,维持在min和max之间,启动的进程数最大不超过serverlimit限制;
  • minsparethreads,maxsparethreads:worker和event中,一个子进程中能启动的线程范围
  • maxrequestworker:能最大接受的并发连接数
  • threadsperchild:每个子进程最多的线程数;

记分板

​ apache httpd采用socreboard记录主进程、子进程、线程的信息;

​ 记分板简单说:就是记录,主进程、子进程、线程状态信息的内存中的数据结构;

​ prefork、worker、event都采用scoreboard;且记分板可以用于进程间,线程间通信方式之一;

​ 记分板信息可以用mod_status模块查看;加载模块,添加配置:

httpd -M |grep status 看有无启动staus模块;

<Location "/server-status">
	SetHandler server-staus
	Require all granted
</Location>
apachectl fullstatus查看;
【需依赖links命令行浏览器包】

下为浏览器查看的信息:
Apache Server Status for www.a.com (via 192.168.80.101)

Server Version: Apache/2.4.6 (CentOS)
Server MPM: prefork
Server Built: Apr 2 2020 13:13:23

Current Time: Monday, 17-Aug-2020 14:33:19 CST
Restart Time: Monday, 17-Aug-2020 14:31:57 CST
Parent Server Config. Generation: 1
Parent Server MPM Generation: 0
Server uptime: 1 minute 21 seconds
Server load: 0.00 0.01 0.05
Total accesses: 4 - Total Traffic: 3 kB
CPU Usage: u0 s0 cu0 cs0
.0494 requests/sec - 37 B/second - 768 B/request
2 requests currently being processed, 4 idle workers

全局记分板

typedef struct {
    int             server_limit;
    int             thread_limit;
    ap_generation_t running_generation; /* the generation of children which
                                         * should still be serving requests.
                                         */
    apr_time_t restart_time;
} global_score;

可看到进程、线程的硬限制数;
进程的代数:即子进程第几代了;
httpd启动时,会先创建N个槽位,然后创建子进程,每建一个子进程,就占用一个slot槽位,杀一个子进程,槽位就空出来一个,因此子进程的上限数就是slot数量;

子进程记分板

struct process_score {
    pid_t pid;
    ap_generation_t generation; /* generation of this child */
    char quiescing;         /* the process whose pid is stored above is
                             * going down gracefully
                             */
    char not_accepting;     /* the process is busy and is not accepting more
                             * connections (for async MPMs)
                             */
    apr_uint32_t connections;       /* total connections (for async MPMs) */
    当前进程的总连接数
    apr_uint32_t write_completion;  /* async connections doing write completion */
    写等待数量
    apr_uint32_t lingering_close;   /* async connections in lingering close */
    关闭状态的连接套接字
    apr_uint32_t keep_alive;        /* async connections in keep alive */
    长连接状态
    apr_uint32_t suspended;         /* connections suspended by some module */
    被挂起的连接
    int bucket;             /* Listener bucket used by this child */
};

线程记分板

​ event模型下,部分记分板信息;

   Slot  PID  Stopping   Connections    Threads      Async connections
                       total accepting busy idle writing keep-alive closing
   0    42480 no       25    yes       25   0    1       0          0
   1    42481 no       26    yes       25   0    2       0          0
   2    42482 no       27    yes       25   0    3       0          2
   3    42564 no       27    yes       24   1    2       0          1
   4    42618 no       28    yes       25   0    3       0          1
   5    42651 no       28    yes       24   1    1       0          2
   6    42652 no       26    yes       25   0    1       0          1
   7    42709 no       26    no        25   0    1       0          1
   8    42710 no       27    no        24   1    1       0          1
   9    42711 no       26    yes       25   0    1       0          1
   10   42712 no       26    no        25   0    2       0          0
   11   42824 no       26    yes       25   0    0       0          2
   12   42825 no       26    yes       25   0    1       0          1
   13   42826 no       27    yes       22   3    2       0          0
   14   42827 no       28    yes       25   0    1       0          2
   15   42828 no       26    yes       25   0    1       0          0
   Sum  16    0        425             394  6    23      0          15
   
   可以看出:
   当前有16个子进程;16个槽位;
   connections部分:
   	total,该进程当前连接总数
   	accepting,是否可以再接收连接,no的当前不能参数到最近的监听资格的争抢中,【争了也接收处理不了】
   	
   threads部分:
   	busy,在忙的线程数
   	idle,空闲线程数
   
   Async connections部分:
   	 writing :写等待托管
   	 keep-alive :空闲长连接托管
   	 closing:关闭状态托管:
   	 

一些概念

graceful stop

​ 优雅的关闭,即给进程处理后事的时间,关闭监听,不再接收新连接,处理完当前连接后再从容的断开;

​ nginx/httpd中,都可接收WINCH、USR1信号,分别表示graceful stop,graceful restart;能接收是因为程序实现了对对应信号的处理逻辑,头文件mpm_common.h有如下代码;

/* Signal used to gracefully restart */
#define AP_SIG_GRACEFUL SIGUSR1

/* Signal used to gracefully stop */
#define AP_SIG_GRACEFUL_STOP SIGWINCH

惊群

​ 举例来说,没有端口重用时,在httpd的worker模式,多个工作子进程的监听线程轮流监听一个套接字文件,同一个时刻只能一个监听线程在监听,当该监听线程监听到一个连接后,解除对套接字文件的监听,到后面做处理时,此时会把其他所有监听线程唤醒,通过一种机制选中下一个监听线程,叫醒那么多,确只有一个能上位,这就叫惊群;如此反复,一次把所有监听线程都叫醒了,太浪费,

​ 于是通过事件的方式,一是只叫醒一个可以监听的线程就避免了惊群;

异步连接/异步io

​ event是在worker模式之上的改进,采用异步连接,异步连接和异步io概念不同,异步连接简单理解,一个工作线程接入一个客户端连接后,不是一条龙把它服务好了自己再接下一个客,也就是不会从头服务到尾,尤其是长连接下,空闲时间,会把该连接套接字托管给监听线程,【自己再去等着准备服务其他的客人】当再有请求发来时,该套接字可通过事件方式主动报告,我这又有活儿了,此时监听线程会再分配一个空闲的工作线程为它服务;这个工作线程和上一个线程可不一定一样;空闲的长连接套接字、closing状态连接套接字、写等待连接套接字都会交给监听线程代为管理

​ 写等待连接套接字:就是,工作线程处理好数据后若回复的数据较大,或此时socket buffer的send buffer满了,没地儿了,写入调用会返回EWOULDBACK错误,此时就得等会,那工作线程干等多浪费资源,就把这个写等待状态的连接套接字委托给监听线程;当send buffer有地方了,这个套接字又可以主动报告,我这可以写了,于是监听线程再给它这个套接字分配一个闲的工作线程进行处理;将响应数据发送到send buffer中等待从网卡发出;

​ 这是event模型的重点;

端口重用

​ 一个套接字ip:port 可以分配多个套接字文件fd,使得多实例、多进程、多线程,可以同时监听一个套接字,而不用采用锁机制进行争抢;采用的是轮询机制接入客户端请求,如ssd可以启动2个实例,实例都监听在本机的所有地址的22端口,2个套接字描述符轮询接收请求;自然2个实例也是轮询接收请求;

进程/线程

​ 1个重要区别,【同一个进程间的】线程间切换比较节省资源;进程间切换就比较效率低了,因为cpu分配给的是进程,而不是线程,一个cpu时间片内,该进程内部的线程切换,仍然是在一个cpu时间片内,切换的上下文资源也较少;进程更重量,而线程更不安全,一个线程可能会影响其他线程的数据;

updatedupdated2020-10-162020-10-16
加载评论