nginx 网络框架浅析

本文深入探讨了nginx的事件驱动框架,重点讲解了nginx如何集成各种操作系统的事件框架,特别是基于linux的epoll。详细介绍了nginx的主循环框架ngx_cycle_s、事件结构ngx_event_s、连接管理ngx_connection_s以及事件模块ngx_event_module_s。文章还阐述了epoll事件模块的实现、过期事件的处理机制和nginx的惊群处理及负载均衡策略。
摘要由CSDN通过智能技术生成

nginx 事件驱动框架浅析

Nginx的网络框架集成了各个操作系统的事件框架,这里主要基于linux 的 epoll 讲解。 一个事件处理框架所要解决的问题是如何收集、管理、分发事件。这里所说的事件,主要以网络事件和定时器事件为主,而网络事件中又以TCP网络事件为主。本文主要结合nginx源码解析整个事件驱动框架的机制。
参考书目《深入理解nginx》。

nginx主循环框架 ngx_cycle_s

nginx 有一个全局的对象 ngx_cycle_s, 这个结构体囊括了所有的核心结构体,并控制这个进程的运行。比如 nginx 的配置文件,所有模块,以及连接,事件,内存池,日志等等,都存在 nginx_cycle_s 结构体中。由于这里只介绍 nginx 的事件驱动机制,因此只列出与之相关的一部分字段。

struct ngx_cycle_s {
   
    ...
    //ngx_listening_t 动态数组,存储需要监听的端口,初始化时会根据数组内容监听
    ngx_array_t               listening;
    //所有的连接
    ngx_connection_t         *connections;
    //所有注册的读事件
    ngx_event_t              *read_events;
    //所有注册的写事件
    ngx_event_t              *write_events;
    ...
};

以下是启动流程
在这里插入图片描述

nginx事件 ngx_event_s

一个事件驱动框架包括,事件产生者,事件收集分发者,和事件消费者。

在这里插入图片描述

有了事件注册分发器,还需要对每一个事件进行封装。事件是注册和分发的实体,网络事件也好,定时器事件,信号事件也好,在 nginx 中都抽象成了结构体 ngx_event_s 。这里列出了结构体一部分的字段和含义。

struct ngx_event_s {
   
    /*data通常指向一个ngx_connection_t连接对象。开启文件异步I/O时,它可能会指向ngx_event_aio_t结构体*/
    void            *data;
    
    /*是否可写标志位。通常情况下表示对应的TCP连接目前状态是可写的,也就是连接处于可以发送网络包的状态*/
    unsigned         write:1;
    
    /*是否可接受连接标志位。通常情况下,在ngx_cycle_t中的
listening动态数组中,每一个监听对象ngx_listening_t对应的读事件中的accept标志位才会是1*/
    unsigned         accept:1;

    /*当前事件是否过期标志位。仅用于事件驱动模块,事件消费模块可不用关心。为什么需要这个标志位呢?当开instance标志位来避免处理后面的已经过期的事件。在9.6节中,将详细描述ngx_epoll_module是如何使用instance标志位区分过期事件的,这是一个巧妙的设计方法*/
    unsigned         instance:1;

    /*是否活跃标志位。这个状态对应着事件驱动模块处理方式的不同。例如,在添加事件、删除事件和处理事件时,active标志位的不同都会对应着不同的处理方式。在使用事件时,一般不会直接改变active标志位*/
    unsigned         active:1;
...
    /*最核心的事件处理函数:该回调函数由每个事件消费模块自己实现。
    typedef void (*ngx_event_handler_pt)(ngx_event_t *ev);
    */
    ngx_event_handler_pt  handler;
....
};

nginx 为了提高性能,读写事件都是预先生成的。在启动过程中,会在 ngx_cycle_t 结构体中预先分配好读事件和写事件,存储在 ngx_cycle_t->read_events 和 ngx_cycle_t->write_events 中。事实上,不仅仅是事件,连连接也是预先分配的,每一个连接会自动对应一个读事件和一个写事件。这个对应关系在后面会讲到。
事件添加和删除操作,可以调用事件模块中的 add() 和 del() 来实现,但是并不推荐这样做,因为,事件类型的不同,事件模块使用的add() del() 等操作也不一样。nginx 提供了更简单和简洁的操作接口,会屏蔽掉底层事件的差异,大部分场景使用这2个接口就可以了。

//将读事件注册要事件驱动模块中
ngx_int_t ngx_handle_read_event(ngx_event_t *rev, ngx_uint_t flags);
//将写事件注册要事件驱动模块中
ngx_int_t ngx_handle_write_event(ngx_event_t *wev, size_t lowat);

nginx连接 ngx_connection_s

连接分为2类,一类是被动连接,也就是服务端监听时,accept() 收到的连接,属于服务端连接,nginx 中用 ngx_connection_s 表示。一类是主动连接,就是 connect() 获得的连接,属于客户端连接,用 ngx_peer_connection_s 。连接和事件的关系前文说了,每个连接都会对应一个读事件和写事件。那么连接池如何和读写事件对应起来呢?答案是使用数组。连接池,读事件,写事件由3个大小相同的数组存储,取不同数组的相同下标即可获得一个连接以及对应的读写事件。

在这里插入图片描述

nginx事件模块 ngx_event_module_s

事件模块是对不同的操作系统事件驱动框架的抽象。因为不同的系统平台提供的事件驱动API不一样,比如linux 下有 select/poll/epoll,macOS下有 kqueue, windows下是IOCP。ngx_event_module_s 提供了统一的模板,即所有的事件框架都是一个 ngx_event_module_s 类型的变量。

typedef struct {
   
    //事件模块的名称,比如 epoll/select 等
    ngx_str_t *name;
    //create_conf和init_conf方法的调用可参见图9-3
    //在解析配置项前,这个回调方法用于创建存储配置项参数的结构体
    void *(*create_conf)(ngx_cycle_t *cycle); 
    //在解析配置项完成后,init_conf方法会被调用,用以综合处理当前事件模块感兴趣的全部配置项
    char *(*init_conf)(ngx_cycle_t cycle, void conf); 
    //对于不同的事件驱动框架需要实现的10个抽象方法
    ngx_event_actions_t actions;
} ngx_event_module_t;

每个事件驱动框架需要自定义对事件的 actions。ngx_event_actions_t 是一组操作集,包括对事件的10种操作。

typedef struct {
   
    /*添加事件方法,它将负责把1个感兴趣的事件添加到操作系统提供的事件驱动机制(如 epoll、kqueue等)中,这样,在事件发生后,将可以在调用下面的process_events时获取这个事件*/
    ngx_int_t  (*add)(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags);
    /*删除事件方法,它将把1个已经存在于事件驱动机制中的事件移除,这样以后即使这个事件发生,调用process_events方法时也无法再获取这个事件*/
    ngx_int_t  (*del)(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags);
    /*启用1个事件,目前事件框架不会调用这个方法,大部分事件驱动模块对于该方法的实现都是与上面的add方法完全一致的*/
    ngx_int_t  (*enable)(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags);
    /*禁用1个事件,目前事件框架不会调用这个方法,大部分事件驱动模块对于该方法的实现都是与上面的del方法完全一致的*/
    ngx_int_t  (*disable)(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags);
    /*向事件驱动机制中添加一个新的连接*/
    ngx_int_t  (*add_conn)(ngx_connection_t *c);
    /*从事件驱动机制中移除一个连接的读写事件*/
    ngx_int_t  (*del_conn)(ngx_connection_t *c, ngx_uint_t flags);

    /*仅在多线程环境下会被调用。目前Nginx不会以多线程方式运行,忽略*/
    ngx_int_t  (*notify)(ngx_event_handler_pt handler);
    
    /*在正常的工作循环中,将通过调用process_events方法来处理事件。这个方法仅在ngx_process_events_and_timers方法中调用,它是处理、分发事件的核心*/
    ngx_int_t  (*process_events)(ngx_cycle_t *cycle, ngx_msec_t timer,
                                 ngx_uint_t flags);

    /*初始化事件驱动模块*/ 
    ngx_int_t  (*init)(ngx_cycle_t *cycle, ngx_msec_t timer);
    /*退出事件驱动模块前调用的方法*/ 
    void       (*done)(ngx_cycle_t *cycle);
} ngx_event_actions_t;
epoll事件模块 ngx_epoll_module

了解 epoll 的都知道,epoll 编程中,需要事先生成一个 epoll_fd 的对象,然后在一个循环里里不断调用 epoll_wait() 来获取就绪的事件,并分发到不同的消费者上去处理。而每当有新的事件需要关注时,就继续往 epoll_fd 注册,这个系统生成的 epoll_fd 句柄实际上就是一个事件的收集器和分发器。所有的事件需要通过 epoll_fd 才能注册到底层的网络硬件上,而当网卡或磁盘等硬件接受了事件的发生时,就会回调给操作系统,操作系统的再通过 epoll_wait() 返回给应用层ÿ

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值