如何提高Web服务器的并发连接处理能力
大概有几个基本条件:
- 基于线程,即一个进程生成多个线程,每个线程响应用户的每个请求。
- 基于事件的模型,一个进程处理多个请求,并且通过epoll机制来通知用户请求完成。
- 基于磁盘的AIO(异步I/O)
- 支持mmap内存映射,mmap传统的web服务器,进行页面输入时,都是将磁盘的页面先输入到内核缓存中,再由内核缓存中复制一份到web服务 器上,mmap机制就是让内核缓存与磁盘进行映射,web服务器,直接复制页面内容即可。不需要先把磁盘的上的页面先输入到内核缓存去。
Nginx 采用的是多进程(单线程) + 多路IO复用模型,就成了”并发事件驱动“的服务器。
多进程的工作模式
- Nginx 在启动后,会有一个 master 进程和多个相互独立的 worker 进程。
- 接收来自外界的信号,向各worker进程发送信号,每个进程都有可能来处理这个连接。
- master 进程能监控 worker 进程的运行状态,当 worker 进程退出后(异常情况下),会自动启动新的 worker 进程
- worker 进程数,一般会设置成机器 cpu 核数。因为更多的worker 数,只会导致进程相互竞争 cpu,从而带来不必要的上下文切换。
【总结】: 使用多进程模式,不仅能提高并发率,而且进程之间相互独立,一个 worker 进程挂了不会影响到其他 worker 进程。
惊群现象
主进程(master 进程)首先通过 socket() 来创建一个 sock 文件描述符用来监听,然后fork生成子进程(workers 进程),子进程将继承父进程的 sockfd(socket 文件描述符),之后子进程 accept() 后将创建已连接描述符(connected descriptor)),然后通过已连接描述符来与客户端通信。
由于所有子进程都继承了父进程的 sockfd,那么当连接进来时,所有子进程都将收到通知并“争着”与它建立连接,这就叫“惊群现象”。大量的进程被激活又挂起,只有一个进程可以accept() 到这个连接,这当然会消耗系统资源。
Nginx 提供了一个 accept_mutex 这个东西,这是一个加在accept上的一把共享锁。即每个 worker 进程在执行 accept 之前都需要先获取锁,获取不到就放弃执行 accept()。有了这把锁之后,同一时刻,就只会有一个进程去 accpet(),这样就不会有惊群问题了。accept_mutex 是一个可控选项,我们可以显示地关掉,默认是打开的。
IO多路复用模型epoll
- epoll() ,内核维护一个链表,epoll_wait 直接检查链表是不是空就知道是否有文件描述符准备好了。内核实现epoll 是根据每个 sockfd 上面的与设备驱动程序建立起来的 回调函数 实现的。那么,某个 sockfd 上的事件发生时,与它对应的回调函数就会被调用,来把这个 sockfd 加入链表,其他处于“空闲的”状态的则不会。
- select() ,内核采用 轮训 的方法来查看是否有fd 准备好,其中的保存 sockfd 的是类似数组的数据结构 fd_set,key 为 fd,value 为 0 或者 1。
- poll()
【总结】:epoll 与 select 相比最大的优点是不会随着 sockfd 数目增长而降低效率。
相关知识参考:
进程和线程的区别:
https://www.cnblogs.com/zhehan54/p/6130030.html-
Apache的三种工作模式:
http://balich.blog.51cto.com/6641781/1743798
https://www.cnblogs.com/zhichaoma/articles/7784688.html- Prefork 多进程
- Worker 多进程+多线程
- Event 多进程+多线程+epoll
Apache常用操作
httpd -V
常看版本、配置信息
apachectl –l
查看Apache当前工作模式
在configure时,可以通过指定参数,将工作模式设置为worker模式或prefork模式。
使用命令:./configure –with-mpm=worker
cd /etc/apache2
切换工作目录
sudo apachectl -k stop
// 关闭 apache 服务器
sudo apachectl -k start
// 启动 apache 服务器
其他
- epoll底层结构
struct eventpoll{
....
/*红黑树的根节点,这颗树中存储着所有添加到epoll中的需要监控的事件*/
struct rb_root rbr;
/*双链表中则存放着将要通过epoll_wait返回给用户的满足条件的事件*/
struct list_head rdlist;
....
};
从上面的讲解可知:通过红黑树和双链表数据结构,并结合回调机制,造就了epoll的高效。
OK,讲解完了Epoll的机理,我们便能很容易掌握epoll的用法了。一句话描述就是:三步曲。
第一步:epoll_create()系统调用。此调用返回一个句柄,之后所有的使用都依靠这个句柄来标识。
第二步:epoll_ctl()系统调用。通过此调用向epoll对象中添加、删除、修改感兴趣的事件,返回0标识成功,返回-1表示失败。
第三部:epoll_wait()系统调用。通过此调用收集收集在epoll监控中已经发生的事件。
- epoll为什么使用红黑树
因为epoll要求快速找到某个句柄,因此首先是一个Map接口,候选实现:
哈希表 O(1)
红黑树 O(lgn)
跳表 近似O(lgn)
据说老版本的内核和FreeBSD的Kqueue使用的是哈希表.
个人理解现在内核使用红黑树的原因:
哈希表. 空间因素,可伸缩性.
(1)频繁增删. 哈希表需要预估空间大小, 这个场景下无法做到.
间接影响响应时间,假如要resize,原来的数据还得移动.即使用了一致性哈希算法,
也难以满足非阻塞的timeout时间限制.(时间不稳定)
(2) 百万级连接,哈希表有镂空的空间,太浪费内存.
跳表. 慢于红黑树. 空间也高.
红黑树. 经验判断,内核的其他地方如防火墙也使用红黑树,实践上看性能最优.