之前粗略阅读过《Unix网络编程》,这本教材里面是基于底层的Socket API实现了一些多进程/多线程模式简单的tcp server。Nginx在linux环境下的底层实现肯定也是基于这些基本的socket API做的,但是这些socket API函数都藏在代码的海洋里,需要花些功夫才能将思路给理清楚。接下来整理整理nginx源码中的这些socket API被调用的位置,并且对相应Nginx的事件框架处理流程做一下梳理。
一.最基本的TCP server的程序结构
最基本的TCP server的程序流程如下:
socket ()---->bind()---->listen()----->进入一个无限的循环
在循环中重复:accept()------->recv/write()。
二.Nginx中对应的函数入口
socket() bind() listen() -----------》 ngx_open_listening_sockets()------------》ngx_init_cycle()----------->main.c
如上所示,nginx在启动之后(创建子进程之前)会解析配置文件,把它需要监听的端口加入listening数组。然后针对配置文件中表明需要监听的每个端口做好bind listen这些初始化操作。
worker进程----------->ngx_event_process_init()----------> ngx_add_event() 这些函数是在初始化的时候将监听事件的fd句柄在epoll中进行注册,并且注册其handler(如果不加accept_mutex互斥锁)
worker进程------------>ngx_process_events_and_timers()---------->ngx_trylock_accept_mutex(cycle)----------》ngx_add_event 如果 加了accept_mutex互斥锁,那么只有在进程抢到互斥锁之后才会加监听事件的句柄在epoll中进行注册
worker进程------------>ngx_process_events_and_timers()---------->ngx_process_events()
使用epoll作为事件驱动时候,ngx_process_events也就对应着ngx_epoll_process_events,这里面会调用epoll-wait来取得已经注册好的并且有数据的事件。然后会调用该事件自身的handler来处理或者加入post队列暂缓处理。读事件的handler--ngx_event_accept中会最终调用
accept()系统函数来接受请求。
三.Nginx的事件驱动的特性
1.解决“惊群问题”
本书的作者(阿里陶辉)给出的分析解释很漂亮,可以参见博客:http://blog.youkuaiyun.com/russell_tao/article/details/7204260
简单地说,抢互斥锁失败的代价要小于被epoll_wait唤醒但是却抢不到accept代价。所以使用进程互斥锁技术来解决“惊群问题”
2.负载均衡
nginx采用的负载均衡策略很简单,就是每个进程都维护一个ngx_accept_disabled变量,用来表示当前进程处理的连接数目是否超过阈值。如果超过阈值,那么当前进程就不会再去接受新连接。