httpd是如何实现高并发服务的
进行网络通信的时候,需要建立一个 socket
,这是大家都知道的。如果一个套接字只能被一个监听进(线)程监听,那么岂不是同一时刻 httpd
只能处理一个请求,处理完这个请求之后,释放 80
端口在给其他请求使用。可是显然 httpd
没有那么笨,那么 apache httpd
是如何支持高并发的呢?
简单的说就是有 两种套接字 ,一种是 监听套接字 ,供监听进(线)程监听使用。另一种是 链接套接字 ,供传输数据使用。监听进(线)程好比丽春院里面接客的老鸨,而为客人提供弹唱服务的姑娘相当于 worker
模式下的工作线程。老鸨可以有一个,但是姑娘有很多。在 apache httpd
的 worker
模式下,每个进程会产生多个工作线程及一个监听线程。监听线程负责监听,其他工作线程负责传输数据。问题回答完了,就这么简单。
那么 监听套接字 跟 连接套接字 是如何配合工作的呢?
要回答这个问题,需要有两个背景知识做铺垫, 网络编程 及 TCP三次握手 。
背景知识1-网络编程
有过网络编程经验的同学都知道,把’大象放冰箱’一般是如下几个步骤。
socket() bind() listen() accept() recv()/send() close()
大家记住这几个步骤,后面用的到。现在来说TCP的三次握手过程。
背景知识2-TCP三次握手
我们经常见到的TCP三次握手过程都是这样的。
如下图:
上面这张图,只是表面现象。下面我把这张图没有体现出来的细节给各位说一说,这对回答上面的问题至关重要。
TCP
的协议栈中维护着两个队列。一个是半连接队列,一个是全链接队列。
客户端发送 SYN
报文到服务端指定端口比如 httpd
的默认 80
端口;此时客户端的状态变为 SYN-SEND
;服务端收到 SYN
报文之后,将这个连接状态,存储到半连接队列中,同时服务端的状态变更为 SYN-RECV
;然后服务端立刻回一个 ACK
报文给客户端;客户端收到 ACK
之后,回给服务端一个 ACK
,服务端收到这个 ACK
之后,客户端跟服务端都变为 ESTABLISHE
状态;表明TCP三次握手成功。这个时候会把半连接队列中的连接,转移到全链接队列。全链接队列中的连接都是已经完成三次握手的连接。监听进(线)程监听 监听套接字 ,就是工作在上面这个过程的。
全链接队列当中的连接都是可以用来直接进行数据传输的连接了。这个时候可以使用 accept()
函数,将全链接当中的连接取出来,生成连接 socket
,进行数据传输。传输完毕之后,经过四次挥手,关掉连接,释放资源。这就是一次完整的TCP传输过程。
俗话说的好,一图胜千言。我将上面的过程简单的画了一个流程图出来。
图中标绿的部分分别是三次握手过程跟四次挥手过程。这两个阶段发生在内核空间。
在 Linux 2.2
以前, listen()
函数有一个 backlog
的参数,用于设置这两个队列的最大总长度,从 Linux 2.2
开始,这个参数只表示全链接队列的最大长度,而 /proc/sys/net/ipv4/tcp_max_syn_backlog
则用于设置半连接队列的最大长度。 /proc/sys/net/core/somaxconn
则是硬限制已完成队列的最大长度,默认为 128
,如果 backlog
大于 somaxconn
,则 backlog
会被强制等于 somaxconn
。
回到apache httpd
prefork模式
apache httpd
的 prefork
模式会创建子进程处理 http
请求。每个子进程,即负责监听又负责处理数据。也就是说从建立三次握手到数据传输,再到四次挥手,一个子进程负责到底。如果传输的数据量比较大,同时系统高并发有比较高,那就需要建立很多子进程,对服务器的硬件资源是个不小的考验。
worker模式
worker
模式是进线程混合模式,主进程会创建子进程,每个子进程会创建多个工作子线程及一个监听进程。监听进程专门负责处理监听套接字,数据传输过程交给其他工作线程。所以 worker
模式要比 prefork
模式在性能上提高不少。
event模式
event
模式跟 worker
模式基本相同,都是进线程混合模式,不同的是,该模式还会生成一个专门处理 keep-alive
的线程。 keep-alive
虽好,但是某些情况下存在站着茅坑不拉屎的情况,这样可以把保持 keep-alive
的空闲连接资源进行回收。 keep-alive
的问题可以看 我这篇文章 。
结束
既然 event
和 worker
都是进线程混合模式,那么为什么 event
模块的性能要比 worker
模块的性能好很多呢?我将在下篇博文中详细说明。