Nginx框架与模块

来源:互联网 发布:禁毒网络知识答题 编辑:程序博客网 时间:2024/06/02 01:51
Nginx框架与模块

框架

        Nginx架构设计的基础是高度的模块化设计. 除了少量的核心代码, 其他一切都是模块.

         所有的模块都遵循同样的 ngx_module_t接口设计规范. 此结构体中的 ctx 成员是一个 void * 指针. 可以指向不同模块的不同数据, 一般用来表示在不同类型的模块中的一种类型模块所具备的通用性接口.
          配置类型模块(    NGX_CONF_MODULE )是唯一一种只有一个模块的模块类型, 仅有的模块为 ngx_conf_module, 是Nginx最底层的模块, 是其他所以模块的基础
          核心模块也是一种基础类型的模块, NGX_CORE_MODULE. 目前官方的核心类型模块共有6个模块, 为 ngx_core_module, ngx_errlog_module, ngx_events_module, ngx_openssl_module, ngx_http_module, ngx_mail_module.定义这几个核心模块可以简化Nginx的设计, 使得非模块化的代码只要关注如何调用6个核心模块(根据之前对开发模块的认识可以看出, Nginx中大部分模块都是非核心模块)
      
这是核心模块的接口:
typedef struct{        ngx_str_t name;                        //核心模块名字        void *(*create_conf)(ngx_cycle_t *cycle);            //解析配置项前, Nginx框架会调用create_conf方法        char *(*init_conf)(ngx_cycle_t *cycle, void *conf);  //解析完成后, 调用init_conf方法}ngx_core_module_t;
从里面的函数指针可以看出, 此结构体是以配置项的解析作为基础的. 它提供了create_conf回调方法来创建存储配置项的数据结构, 在读取nginx.conf配置文件时候, 会根据模块中的ngx_command_t把解析出来的配置项存储在这些被创建的数据结构中.init_conf方法用于在解析完配置文件后, 使用解析出的配置项初始化核心模块功能
         这种设计使得每个核心模块都可以自由的定义全新的模块类型.  所以, 对于各个核心模块, 比如ngx_events_module定义了NGX_EVENT_MODULE模块类型, 所以该类型的模块都由ngx_events_module核心模块管理; 再比如 ngx_http_module定义的NGX_HTTP_MODULE类型同理.        


模块

         所有的模块间是分层次分类别的, 官方Nginx共有5大类型的模块: 核心模块(上面就是对核心模块的一些分析), 配置模块, 事件模块, HTTP模块, mail模块. 虽然他们都具备相同的 ngx_module_t接口, 但在请求处理流程中的层次并不相同, 比如上面讲的核心模块的ngx_core_module_t, 比如http模块的ngx_http_module_t都会再次具体化ngx_module_t接口. (配置模块只有一个模块, 所以它就不再需要具体化ctx上下文成员)
       Nginx框架定义配置模块和核心模块. 定义配置模块因为配置模块是所有模块的基础, 实现了最基本的配置项解析(解析nginx.conf文件)功能; 不定义其他三钟模块是因为, 他们在核心模块中各有1个模块作为自己的代言人, 并且在同类模块中有一个作为核心业务和与管理功能的模块.比如事件模块来说, 一方面它是由核心模块中作为代言人的ngx_event_module核心模块定义的, 另一方面所有事件模块的加载操作不是由Nginx框架完成的, 而是由ngx_event_core_module负责的. 对于HTTP模块来说, HTTP模块是由它的代言人ngx_http_module核心模块定义的, 但与事件模块不同的是, 这个核心模块还负责加载所有HTTP模块, 但业务核心逻辑以及具体请求选用哪个http模块处理则是由ngx_http_core_module模块来决定.
         所以, 配置模块与核心模块都是与Nginx框架密切相关的, 是其他模块的基础. 而事件模块则是HTTP模块和mail模块的基础
                    
      
事件驱动架构
         顾名思义, 就是由一些事件发生源(对于Nginx, 一般为网卡, 磁盘)来产生事件, 由一个或者多个事件收集器(事件模块)来收集, 分发事件, 然后许多事件处理器(任何一个模块都可能)会注册自己感兴趣的事件, 同时消费这些事件.
         这与传统的web服务器是不同的, 对于传统的服务器, 每个请求在连接建立之后都始终占用着系统资源, 直到连接关闭后才释放资源, 往往把一个进程或一个线程当作事件消费者,
       Nginx则不然, 所谓的事件消费者只能是某个模块. 只有事件收集分发器才有资格占用进程资源, 会在分发某个事件时调用事件消费模块使用当前占用的资源.
         但是, Nginx中每个消费者都不能有阻塞行为, 否则将由于长时间的占用分发者进程导致其他事件得不到响应. 尤其是每个事件消费者不可已让进程转变为休眠状态或等待, 这样就无形增加了事件消费者程序开发的难度.


请求的多阶段异步处理
        异步处理和多阶段处理是相辅相成的, 只有把请求分为多个阶段, 才有所谓的异步处理. 也就是说, 当一个事件被分发到事件消费者中进行处理时, 事件消费者处理完这个事件只相当与处理完一个请求的某个阶段, 下一个阶段的处理只能等内核的通知. 这样每个阶段的事件消费者都不清除完整的操作到底什么时候结束, 只能异步被动的等待下一次事件的通知
        这种设计被不会或尽量少的出现进程休眠的情况, 一旦出现休眠, 必定减少并发处理事件的数目, 从而降低网络利用率, 同时增加请求处理的平均时延. 此时就可能需要增加进程数目来解决问题, 但进程数目过多就会导致频繁的进程间切换消耗CPU进而降低网络性能. 同时, 一直得不到释放的内存资源被该进程一直占用, 导致可用内存下降, 影响最大并发数.

    那么根据什么原则来划分请求的阶段呢?
        1. 将阻塞的方法按照相关的触发事件分解.
              大部分情况下, 一个阻塞进程的方法调用时可分为两个阶段: 阻塞方法改为非阻塞;  增加新的处理阶段用于处理非阻塞方法的最终返回结果.
              比如用send调用数据发送给用户时, 此时使用非阻塞socket, 这样调用send后进程不会进入休眠, 这就是发送且不等待结果阶段; 再把socket加入到事件收集器中就可以等待相应的事件触发下一个阶段, send发送的数据被对方接收后这个事件就会触发send结果返回阶段.
        2. 将阻塞方法调用按照时间分解为多个阶段的方法调用.
              比如在进行磁盘操作, 因为epoll不支持磁盘事件, 无法将阻塞方法按照触发事件分解, 所以采用部分读取的方式. 那么下一次的部分如何触发呢? 如果有对应的网络事件发生那么正好用来触发; 如果没有的话, 可以设置简单的定时器,在某个时间点再次地调用下个阶段.
        3. 如果阻塞的方法无法完全继续划分, 则必须使用独立的进程执行这个阻塞的方法.


0 0
原创粉丝点击