1、Nginx对epoll的完美使用
上一章节Redis 5.0是单线程的使用epoll,本章节我们来看Nginx在多线程下对epoll的使用
多核时代的需求
上一章中,Redis所有复杂逻辑都在单线程中,这种模式应用与数据缓存是可以,但是迁移到不同应用如webserver,可能就不行了,我们需要一种可以使用所有CPU资源的方式
但是一旦使用多线程就会有一下问题
哪个进程 accept 接收新连接?
哪个进程负责发现用户连接上的读写事件?
当有用户请求到达的时候,如何均匀地将请求分散到不同的进程中?
需不需要单独搞一部分进程执行计算工作
这就是多线程需要考虑的问题,于是就有了不同的方式如Reactor ,Proactor模式。本章节我们来看看Nginx是采用的怎样的解决思路。
Nginx采用的多进程方案如下图
一个master
多个worker
接下来我们分别来看看master进程和worker进程干了写什么。
Nginx的master进程
我们如图看Redis一样,都先从其main函数看起,如下图
主要关注点在于
ngx_init_cycle
函数和ngx_master_process_cycle
函数
这两个函数最终都会调用ngx_open_listening_sockets
函数,如下图
会发现其中都有对于bind和listen的调用
然后master进程还会调用如下函数,根据配置建立worker进程,会创建多个
在以上我们发现,master进程在网络编程来看只有bind,listen操作,似乎没有epoll的创建epoll对象,accept,监听socket的操作。
Nginx的Worker进程
现在我们先来总得看一下worker进程,如下图
首先是调用worker的初始化函数ngx_worker_process_init。worker里面的功能都是模块化插入配置。
所以后续它会遍历所有的module,执行ngx_event_core_module
执行初始化函数ngx_event_process_init
先是调用epoll module方法,在这个里面会执行epoll_create
接着遍历所有listen的socket,并设置回调函数,执行epoll_ctl
worker初始化完成后,会调用ngx_process_events_and_timers,最终会在这个过程中调用epoll_wait执行
接下来我们来详细的看看master启用worker进程的调用函数
都说Nginx 采用的是一种模块化的架构,那么到底有哪些模块呢
我们本节主要关注的是其网络模块
各个模块的初始化流程都基本如下图
我们来详细看看init_process中调用到的ngx_epoll_init函数(ngx_epoll_module 初始化之创建 epoll 对象)
仅仅只是创建epoll对象还不够,还需要监听socket,并加入epoll的监控。如下图ngx_event_core_module 初始化之注册listen socket 处理
我们来大致看下ngx_event_process_init函数的代码,如下图‘
在epoll_ctl后就需要进入epoll_wait等待接收新连接
等待用户请求
用户请求处理
在worker进程中,在Nginx执行epoll_ctl之前,Nginx是注册了回调函数的。一旦当listen前听到事件是,就会调用这个回调函数
我们来大致看下ngx_eveent_accept函数大致代码
当接受到新连接后,会从全连接队列取出,并调用ngx_add_conn函数
最终发现执行链路还是会调用到epoll_ctl上去
2、总结
最后我们汇总下Nginx与网络编程epoll相关的部分
我们发现其实这个worker的过程和Redis的十分相似,区别就在于Nginx是多进程的,每一个进程都有一个epoll对象,他们监听的端口也是相同的,通过锁竞争的方式决定多个worker谁去accept接收对象。从而分摊压力。但是它也有缺陷,它的连接复用率较低,如何理解呢?
每个worker进程都会保持一种长连接机制
一堆的长连接,假设用户的进程每次都大到同一个worker进程上的话,那么这个worker不需要重新握手,可以直接复用连接就行。而现在大部分是htts,三次握手加上安全协议ssl握手,在网关处会有七次,所以能否复用连接很大决定其性能。一般而言连接都是网关上的复用
但是Nginx的连接复用只能在nginx内部的进程上面,多进程不可能一直都打在一个进程上面,如何有新的worker进程,那么就又会针对这个进程创建连接,从而使Nginx的连接复用率低。
这个现在有个非常明显的例子,就是CloudFlare,但是现在它重构了很多东西。
3、源码阅读
算了,不想写了,看看大佬写的相关文档吧张彦飞-epoll应用案例之Nginx源码分析