学习Nginx看这篇就够了

0. NGINX的优点

  • 响应速度快
    • 单次请求响应快,高并发请求响应速度快
  • 高扩展性
    • 低耦合的模块设计框架使得可以扩展大量的第三方模块
  • 高可靠性
    • 每个worker进程相对独立
    • master进程在一个worker进程挂掉后,会快速启动新worker进程提供服务
  • 低内存消耗
    • 1w个非活跃的http keepalive连接仅消耗2.5M的内存
  • 单机支持10W+的并发连接
  • 支持热部署
    • master管理进程与worker进程分离设计,使得nginx能够支持热部署功能
  • 支持free bsd
  • 总的一句,nginx能在支持高并发请求的同时保持高效的服务

1. 配置编译安装

1.1 配置

  • 语法:./configure + 编译选项
  • 编译选项:
    • 路径相关参数:带path的参数,如安装路径
    • 编译相关参数:编译器相关的参数
    • 依赖第三方库相关参数
      • pcre:支持正则表达式
      • zlib:gzip格式压缩库
      • openssl:ssl加密库
    • 模块相关参数:决定模块是否需要编译到nginx中
      • 默认编译进nginx的模块,如事件模块
      • 默认不编译进nginx的模块,如邮件代理相关模块
      • 自定义第三方模块
  • 示例:
    ./configure --prefix=/usr/local/nginx 
    --with-http_realip_module 
    --with-http_addition_module 
    --with-http_gzip_static_module 
    --with-http_secure_link_module 
    --with-http_stub_status_module 
    --with-stream 
    --with-pcre=/home/develop/nginx/pcre-8.41 
    --with-zlib=/home/develop/nginx/zlib-1.2.11 
    --with-openssl=/home/develop/nginx/openssl-1.1.0g 
    --add-module=/home/develop/nginx/nty_module
    

1.1编译&部署

  • make:编译
  • make install:部署
  • 执行细节
    • 配置阶段会执行auto目录下的相关脚本,检测环境,参数解析,生成中间目录、部分C文件、makefile
    • 执行auto/modules脚本会生成 ngx_modules.c
      • 文件中modules数组指明每个模块在nginx中的优先级
        • filter模块在数组中位置越靠后,优先级越高
        • 除filter模块外其他模块在数组中位置越靠前,优先级越高

2. 命令行控制

  • 语法:./nginx + 命令行选项
  • 命令行选项
    • -c 指定配置文件nginx.conf
    • -p 指定安装目录
    • -g 临时指定一些全局配置项以使新的配置项生效
    • -t 不启动nginx的情况下,用-t测试配置文件是否有误
    • -q 测试配置选项时,-q可以不把error等级下的信息输出到屏幕
    • -v 显示版本信息
    • -V 显示版本 + 显示配置编译阶段的信息
    • -s stop 强制停止nginx服
    • -s quit 处理完当前所有请求后再停止服务
    • -s reload nginx重新加载nginx.conf文件
    • -s resopen 日志文件回滚
    • -h 显示帮助

3. 配置

3.1 配置文件

  • 块配置项:有event{},http{},server{},location{}
  • 块配置项可以嵌套使用,当内层块与外层块的同一配置项出现冲突时,以内存块的配置项为准
  • 配置项组成:
    • 配置项名 配置项值1 配置项值2 …; (每行配置结尾都要加上分号)
  • 配置项注释:配置行首加#号注释掉

3.2 负载均衡配置

  • upsteam块配置
    • 语法:upstream name { … }
    • 示例:
    upstream backend {
        server 192.168.0.1:80;
        server 192.168.0.2:8888;
        server backend.exaple.com;
    }
    
  • server参数配置
    • 语法:server name [weight | max_fails | fail_timeout | down | backup]
      • weight=number : 权重
      • max_fails=number : 与fail_timeout 配合使用,表示在fail_timeout超时时段内,如果向上游服务器转发 失败的次数达到max_fails,则认为该上游服务器不可用,默认为1,为0表示不检测失败次数
      • fail_timeout=time : 转发失败超时时长,默认为10秒
      • down : 表示上游服务器永久下线,只有配置了ip_hash时该项才生效
      • backup :表示所在的上游服务器是备份服务器,如果配置ip_hash,则该项失效
    • 示例:
      upstream  backend {
          server  192.168.0.1:80 weight=5;
          server  192.168.0.2:8888 max_fails=10 fail_timeout=30s;
          server  backend.exaple.com;
      }
      
  • ip_hash 参数配置
    • 使用方法:
      • 首先根据客户端的ip地址计算出key,将key按照upstream集群里的服务器数量进行取模
      • 然后根据取模后的结果将请求转发到相应的服务器上
      • 这样就能确保同一个客户端的请求只会转发到相同的服务器上
    • 注意点:
      • ip_hash 和 weight 不可同时使用,
      • 若upstream集群中有服务器下线,不可直接删除配置,需要server配置项后加上 down标识, 以确保转发策略一致
    • 示例:
      upstream  backend {
          ip_hash;
          server  192.168.0.1:80  down;
          server  192.168.0.2:8888;
          server   backend.exaple.com;
      }
      

3.3 反向代理配置

  • proxy_pass URL : 将当前请求反向代理URL参数指定的服务器上
    • 示例:proxy_pass http://192.168.0.1:8080/uri/;
    • 注意:默认情况下反向代理是不会转发请求中的host头部,如需转发则要加上:proxy_set_header Host $host
  • proxy_method method: 表示转发时的协议方法名,如 proxy_method POST
  • **proxy_hide_header header **: 指定哪些头部不能被转发
  • proxy_pass_header header: 指定哪些头部可以被转发
  • **proxy_pass_request_body [on | off] ** : 确定是否需要转发http包体
  • proxy_pass_request_header [on | off ] :确定是否需要转发http头部
  • proxy_redirect [default | off | redirect replacement]
    • 当上游服务器返回301重定向或302刷新请求时,proxy_redirect 可以重设http头部中的location和refresh字段
  • proxy_next_upstream:代理到下一个转发服务器

3.4 配置文件编写示例

worker_processes 4;    //启动多少个worker进程,一般和CPU核数相同
events {               //对应事件,每一个连接对应的可读可写事件
    worker_connections  1024;  //每个进程响应的连接数
    worker cpu affininity 1000 0100 0010 0001;
}

#main配置块
http {
    #负载均衡的配置
    upstream backend {
        server 192.168.142.128 weight=1;   #weight表示权重
        server 192.168.142.129 weight=2;
    }
    
    #server配置块
    server {   #协议对应的server,server可以多个
         listen 8888;  #监听的端口
        server_name  localhost;  #主机名称
        client_max_body_size 100m;  #客户端请求body的最大数量
        location / {  #请求的目录结构
#           root /usr/local/nginx/html 
#           proxy_pass http://192.168.142.128;
            proxy_pass http://backend;
        }
        #请求静态资源(js/css/png/video&audio)
        location /images/ {
            root /usr/local/nginx/;
        }
        location ~ \.(mp3|mp4){
            root /usr/local/nginx/media/;   
        }
    }
}
  • 配置文件处理源码流程:
    学习Nginx看这篇就够了

4. 源码目录结构

  • auto/:存放配置编译阶段用到的脚本
  • src/:核心源码
    • src/core:核心代码,连接相关、crc、和一些基础数据结构
    • src/event:事件处理相关
    • src/os:操作系统相关
    • src/http:http协议相关
    • src/mail:邮件相关
    • src/stream:tcp流相关

5. 数据结构

  • 核心结构体:

    • ngx_cycle_t

      • nginx框架是围绕着ngx_cycle_t结构体来控制进程运行的
      • ngx_init_cycle 中 nginx框架会根据配置项加载所有模块来构造ngx_cycle_t结构体中的成员
      • conf_ctx:维护着所有模块的配置结构体,类型是void****,首先指向一个成员皆为指针的数组,其中每个成员指针又指向一个成员皆为指针的数组,第2个子数组中的成员指针才会指向各模块生成的配置结构体
    • ngx_module_t

      • nginx模块的数据结构,其中ctx 和 command 成员最为重要
      • ctx_index:表示当前模块在这类模块中的序号,类型由下面的type成员决定
      • indx : 表示当前模块在所有模块中的序号
      • type : 表示该模块的类型,有五种类型:
        • NGX_HTTP_MODULE
        • NGX_CORE_MODULE
        • NGX_CONF_MODULE
        • NGX_EVENT_MODULE
        • NGX_MAIL_MODULE
      • ctx:http类型的模块需要将 ctx 指向 ngx_http_module_t 定义了读取和重载配置文件的操作函数
      • commands : 用于定义模块的配置文件参数,每组元素都是ngx_command_t 类型,结尾用ngx_null_command表示
  • 高级数据结构

    • ngx_queue_t 双向链表
    • ngx_array_t 动态数组
      • 内置了nginx封装的内存池
      • 特点:
        • 访问速度快
        • 允许元素个数具备不确定性
        • 负责元素占用内存的分配,这些内存将由内存池统一管理
    • ngx_list_t 单向链表
    • ngx_rbtree_t 红黑树
      • ngx_radix_tree_t 基数树
    • 支持通配符的散列表:检索和插入速度的期望时间均为O(1),适合频繁读取,插入,删除元素
      • nginx的散列表使用的是开放寻址法
      • nginx支持查找带前置通配符或者后置通配符的hash
        • ngx_hash_find_wc_head
          -ngx_hash_find_combined查询流程:
        • 先查全匹配–再查前缀匹配–再查后缀匹配

6. 架构设计特点

6.1 模块化设计

  • 配置模块: ngx_conf_module (所有模块的基础)
  • 核心模块: ngx_core_module
  • 事件模块: ngx_events_module
  • http模块: ngx_http_module
  • mail模块: ngx_mail_module

6.2 事件驱动架构

  • 简单说就是由一些事件发生源来产生事件,由一个或多个事件收集器来收集分发事件,然后许多事件处理器会注册自己感兴趣的事件,然后消费这些事件
  • 将对IO管理转换为事件管理
  • 不同操作系统,不同内核版本有不同的事件驱动机制
  • 分类:
    操作系统 事件驱动机制 事件驱动模块
    Linux2.6之前 poll / select ngx_poll_module / ngx_select_module
    Linux2.6之后 epoll ngx_epoll_module
    FreeBSD kqueue ngx_kqueue_module
    Solaris10 eventport ngx_eventport_module

6.3 请求多阶段异步处理

  • 把一个请求的处理过程按照事件的触发方式划分成多个阶段
  • 每个阶段都由事件收集和分发器触发
  • 异步处理和多阶段是相辅相成的,只有把请求分解成多阶段,才有所谓的异步处理

6.4 管理和工作进程分开设计

  • master进程:一个管理进程
    • master进程工作机制
      • master 进程不需要处理TCP网络事件,不负责业务的执行,只
      • 会负责管理worker子进程以实现重启服务,平滑升级,更换日志文件,配置文件实时生效等功能
    • 操作系统通过信号管理master进程
    • master进程信号定义:
      信号 对应进程中的全局标志位变量 意义
      QUIT ngx_quit 优雅地关闭整个服务
      TERM 或 INT ngx_terminate 强制关闭整个服务
      USR1 ngx_reopen 重新打开所有文件
      WINCH ngx_no_accept 所有子进程不再接受处理新的连接,相当于对所有子进程发送QUIT信号
      USR2 ngx_change_binary 平滑升级到新版本的nginx
      HUP ngx_reconfingure 重新配置文件并使服务对新配置生效
      CHLD ngx_reap 有子进程意外结束,监控所有子进程
  • worker进程:多个工作子进程
    • 如何启动子进程:ngx_spawn_process 内部封装了fork

    • worker进程数量一般设置和CPU核数相同

    • 可将每个worker进程绑定到指定CPU核上,提高运行效率,减少进程间切换的代价

    • worker进程提供服务,worker进程间通过共享缓存等机制通信

    • master进程通过信号来管理worker进程

    • worker进程信号定义

      信号 对应进程中的全局标志位变量 意义
      QUIT ngx_quit 优雅地关闭进程
      TERM 或 INT ngx_terminate 强制关闭进程
      USR1 ngx_reopen 重新打开所有文件
      WINCH ngx_debug_quit 目前无实际意义

7. nginx主要模块

7.1 handler事件模块

  • 作用:主要负责事件处理
  • 场景:前端发送请求后,事件模块接收到请求后可直接进行处理,然后将结果返回给前端

7.2 filter过滤器模块:

  • 作用: 主要负责对http响应包进行加工
  • 场景:后端服务器response信令给前端时,过滤器模块会在后端response消息的基础上加入一些特定信息,如加上md5检验码等

7.3 upstream代理转发模块

  • 作用:转发请求,负载均衡,多机(集群)代理
  • 场景:前端发请求给后端服务器,需经upstream模块转发

7. 事件模块

  • 作用:解决如何收集,管理,分发事件

  • 事件分类:

    • 网络事件
    • 定时器事件
  • IO多路复用:操作系统提供的一种事件驱动机制

  • 核心模块

    • ngx_events_module

      • 定义所有事件类型的模块,并定义事件模块都需要实现的ngx_event_module_t接口
      • 管理事件模块生成的配置项结构体,并解析事件类配置
      • 不会解析配置项的参数,只是在出现events{ }配置项后会调用各事件模块解析events{…}块内的配置项
      static ngx_command_t ngx_events_commands[] = {
          { 
              //事件模块只对 “events{...}” 配置项感兴趣
              ngx_string("events"),
              NGX_MAIN_CONF|NGX_CONF_BLOCK|NGX_CONF_NOARGS,
              ngx_events_block,
              0,
              0,  
              NULL},
          ngx_null_command
      };  
      
      static ngx_core_module_t ngx_events_module_ctx = {
          ngx_string("events"),
          // create_conf 和 init_conf方法均为null
          //是因为ngx_events_module并不会解析配置项的参数
          //而是在出现events配置项后调用各具体事件模块去解析events快内的配置项
          NULL,   
          NULL
      };   
      
      ngx_module_t ngx_events_module ={
          NGX_MODULE_V1,
          &ngx_events_module_ctx,
          ngx_events_commands,
          NGX_CORE_MODULE,
          NULL,
          NULL,
          NULL,
          NULL,
          NULL,
          NULL,
          NULL,
          NGX_MODULE_V1_PADDING
      };
      
    • ngx_event_core_module

      • 决定选用哪种事件驱动机制以及如何管理事件
      • 初始化相应的事件模块
      • 不负责tcp网络事件的驱动,故不需要实现action方法,只需实现create_conf和init_conf
      ngx_module_t ngx_event_core_module = {
          NGX_MODULE_V1,
          &ngx_event_core_module_ctx,
          ngx_event_core_commands,
          NULL,
          //核心成员
          //fork之前调用,主要初始化一些变量
          ngx_event_module_init,
          //fork之后,在各worker子进程中调用
          ngx_event_process_init,
          ...
      }
      
  • 核心结构体:

    • ngx_event_module_t
    typedef struct 
    {
        ngx_str_t *name; //事件模块名
        //解析配置项前,调用该回调创建存储配置项参数的结构体
        void *(*create_conf)(.....);
        //解析配置项完后,调用该回调综合处理事件模块感兴趣的配置项
        char *(*init_conf)(.....);
        //定义事件驱动模块的核心方法
        ngx_event_actions_t actioins;
    }ngx_event_module_t;
    
    //核心方法
    typedef struct 
    {
        ngx_int_t (*add)(.....);  //添加事件
        ngx_int_t (*del)(.....);  //删除事件
        ngx_int_t (*add_conn)(.....);  //添加一个新连接
        ngx_int_t (*del_conn)(.....);   //移除一个连接
        ngx_int_t (*process_events)(.....);   //循环处理事件
        ngx_int_t (*init)(.....);   //初始化事件驱动模块
        ngx_int_t (*done)(.....);   //退出事件驱动模块
        ngx_int_t (*enable)(.....);   //启用事件,不常用
        ngx_int_t (*disable)(.....);   //禁用事件,不常用
        ngx_int_t (*notify)(.....);   //不常用  
    }ngx_event_actions_t
    
    //获取配置: 
    void *ngx_get_conf(conf_ctx, ngx_events_module);    
    
    • ngx_event_t
    typedef struct {
        //事件发生时的处理方法,由具体事件模块具体实现
        ngx_event_handler_pt handler;
        ...
    }ngx_event_t
    
  • 核心接口

    • 向事件驱动机制添加读写事件(每个连接都会对应一个读事件和一个写事件)

      /*
      * 将读事件添加到事件驱动模块中
      * rev: 要操作的事件
      * flags: 事件的驱动方式
      */
      ngx_int_t ngx_handle_read_event(ngx_event_t *rev, ngx_uint_t flags); 
      
      /*
      * 将写事件添加到事件驱动模块中
      * wev: 要操作的事件
      * lowat:缓冲区中必须有lowat大小可用空间时,才处理该事件
      */
      ngx_int_t ngx_handle_write_event(ngx_event_t *wev, ngx_uint_t lowat); 
      
  • 事件模块加载流程

    • 初始化所有事件模块的ctx_index序号
    • 分配指针数组,存储所有事件模块生成的配置项结构体指针
    • 调用所有事件模块的create_conf方法
    • 为所有事件模块解析ngxin.conf配置文件
    • 调用所有事件模块的init_conf方法
  • 代码主要流程

    • ngx_master_process_cycle 调用 ngx_start_worker_processes 生成多个工作子进程
    • ngx_start_worker_processes 调用 ngx_worker_process_cycle 创建工作内容,如果进程有多个子线程时,会初始化线程和创建线程工作内容,初始化完成之后
    • ngx_worker_process_cycle 会进入处理循环,调用 ngx_process_events_and_timers
    • ngx_process_events_and_timers 中调用 ngx_process_events 监听事件,并把事件投递到事件队列ngx_posted_events 中,最终会在ngx_event_thread_process_posted中处理事件
  • 实现一个 handler的步骤

    • 编写模块基本结构。包括模块的定义,模块上下文结构,模块的配置结构等
    • 实现 handler 的挂载函数。根据模块的需求选择正确的挂载方式
    • 编写 handler 处理函数。模块的功能主要通过这个函数来完成

8. 事件模块驱动机制

8.1 核心流程:

  • 循环调用 ngx_process_events_and_timer 处理所有事件,包含网络事件和定时器事件
    • 调用事件驱动模块实现的process_events方法处理网络事件
    • 调用ngx_event_process_posted处理两个post队列中的事件
    • 调用ngx_event_expire_timers方法处理定时器事件

8.2 epoll

  • 核心结构体

    struct eventpoll
    {
        //红黑树的根节点,这棵树存储着所有添加到epoll中的事件,也就是epoll监控的事件
        struct rb_root rbr;
        //双向链表 rdlist保存发生IO变化的事件,这些事件将通过epoll_wait返回给用户
        struct list_head rdlist;
        ......
    }
    
  • 核心接口

    • epoll_create:创建一个epoll对象,返回一个epoll句柄
    • epoll_ctl: 向epoll对象中增加(EPOLL_CTL_ADD)、删除(EPOLL_CTL_DEL)、修改(EPOLL_CTL_MOD)连接事件
    • epoll_wait:收集发生变化的连接事件
  • 工作模式

    • LT 水平触发 : EPOLL默认的工作模式,可以处理阻塞和非阻塞socket
    • ET 边缘触发 : NGINX默认的工作模式 效率比 LT 高,但只能处理非阻塞socket
  • ngx_epoll_module模块

    ngx_event_module_t ngx_epoll_module_ctx = {    &epoll_name,    ngx_epoll_create_conf,    ngx_epoll_init_conf,    //actions方法,由具体事件模块具体定义    {        ngx_epoll_add_event,        ngx_epoll_del_event,        ngx_epoll_add_event,        ngx_epoll_del_event,        ngx_epoll_add_connection,        ngx_epoll_del_connection,        NULL,        ngx_epoll_process_events,        ngx_epoll_init,        ngx_epoll_done    }}
    

8.3 负载均衡

  • 机制:单个worker进程处理的连接数达到它最大处理总数的7/8时,就会触发负载均衡,此时该worker进程会减少处理新连接, 其他worker进程会相应增加处理新连接的机会
  • 关键阈值:ngx_accept_disabled
    • 为负数时不进行负载均衡操作,为正数则进行
    • 计算:ngx_accept_disabled = ngx_cycle->connection/8 - ngx_cycle->free_connection_n
  • TCP/IP五层模型中使用的负载均衡工具对比
    • 硬件的F5是根据Mac地址分发实现的负载均衡
    • dns负载均衡原理:将域名映射成多个ip地址,只需要配置好,其他不用管

学习Nginx看这篇就够了

学习Nginx看这篇就够了

8.4 长时间占用accept_mutex锁的问题

  • 定义:nginx连接事件和读写事件不是放在同一个流程中执行的,因为这样会造成进程长时间占用accept_mutex锁,导致其他进程无法处理新连接
  • 解决方法:使用post事件处理机制,
    • 设置两个post队列,将所有读写事件归类放到两个post队列中
      • ngx_posted_accept_events队列:存放新连接事件
      • ngx_posted_events队列:存放普通读/写事件,延后处理
    • 流程:先处理ngx_posted_accept_events队列中的事件,处理完后立即释放ngx_accept_mutex锁,接着再处理ngx_posted_events中的事件
    • ngx_event_process_posted 会调用posted队列中所有事件handler方法

9. 连接事件

9.1 被动连接

  • 核心结构体
typdef struct ngx_connection_s {
    // 以方法指针形式出现,说明每个连接可以采用不同的接收方法
    typedef ssize_t (*ngx_recv_pt)(ngx_connection_t *c, u_char *buf, size_t size);
    typedef ssize_t (*ngx_recv_chain_pt)(ngx_connection_t *c, ngx_chain_t *in, off_t limit);
    typedef ssize_t (*ngx_send_pt)(ngx_connection_t *c, u_char *buf, size_t size);
    typedef ngx_chain_t *(*ngx_send_chain_pt)(ngx_connection_t *c, ngx_chain_t *in, off_t limit);
    ....
}ngx_connection_t;

9.2 主动连接

  • 核心结构

    typedef struct ngx_peer_connection_s {
        ngx_connection_t *connection;
        ...
    }ngx_peer_connection_t;
    

9.3 连接池

  • 定义:

    • 连接在启动阶段已经预分配好,使用时直接从连接池中获取
    • 在 ngx_cycle_t 中的connections 和 free_connections 成员构成一个连接池
      • connections 指向整个连接池数组首部
      • free_connections 指向第一个ngx_connnection_t的空闲连接
    • 所有空闲连接都以data成员作为next指针串成一个单链表
    • 当有连接到来时就从free_connections 指向的链表头获取一个空闲连接,同时free_connections指向下一个空闲连接
    • 释放连接时只需把该链接插入free_connections链表表头
    • 每个连接至少包含一个读事件和写事件,在connections指向的连接池中,每个连接所需的读/写事件都以相同的数组下标对应起来
  • 核心结构

      sturct ngx_cycle_s {    
          connections;        //指向整个连接池数组的首部    
          free_connections;   //指向第一个空闲连接    
          read_events;        //读事件池    write_events;       //写事件池
      }
    
  • 核心接口

    //获取连接
    ngx_connection_t *ngx_get_connection(ngx_socket_t s, ngx_log_t *log);
    //释放连接
    void ngx_free_connection(ngx_connection_t *c);
    

9.4 内存池

  • nginx 为每个tcp连接都分配一个内存池
  • http框架为每个http请求分配一个内存池,请求结束后销毁整个内存池

9.5 instance标志位

  • 利用指针最后一位一定是0的特性,即将最后一位用来表示instance
  • 当调用ngx_get_connectons从连接池中获取一个新连接时,instance标志位会置反
  • 若在ngx_epoll_process_events方法中判断instance发生变化,则认为该事件是过期事件,不予处理

9.6 惊群问题

  • 定义:master进程开始监听web端口,fork出多个worker子进程,这些子进程开始同时监听同一个web端口,而此时当有新的客户端连接到来时,所有子进程都会被唤醒然后抢着接受连接。而其中只有一个子进程能成功建立连接,其他的都会返回accept失败,这些accept失败的子进程被内核唤醒是不必要的,被唤醒后的执行动作也是多余的

  • 分类:

    • accept 惊群:随着版本更新,系统已解决
    • pthead_cond_wait 线程条件等待惊群:随着版本更新,系统已解决
    • epoll_wait惊群
  • 缺点:浪费系统资源

  • 解决方法:

    • 使用进程间的同步锁accept_mutex, 只有获得锁的进程才会去监听连接

    • 该锁是一个自旋锁,获取锁的过程是非阻塞的

      //获取锁ngx_trylock_accept_mutex(&accept_mutex);//获取锁成功后此变量置1,用于通知该进程的其他模块已获得锁ngx_accept_mutex_held = 1; 
      
  • 特殊场景
    当一个进程的epoll_wait 正在处理当前连接或者处理完连接正在解锁时,此时新的连接来了之后,当前进程无法处理新连接,则其他进程的epoll_wait会接替当前进程处理新连接

10. 定时器事件

  • 定时器由nginx自身实现,与内核无关
    • 定时器实现:

      • 底层实现是红黑树 ngx_event_timer_rbtree
      • 红黑树最左边的结点是最有可能超时的事件
      • 核心结构体
        • ngx_event_timer_rbtree 所有定时器事件组成的红黑树
        • ngx_evebnt_timer_sentinel 红黑树的哨兵节点
      • 核心接口
      //初始化定时器
      ngx_int_t ngx_event_timer_init(ngx_log_t *log);
      
      //查找出红黑树最左边的节点
      ngx_msec_t ngx_event_find_timer(void);
      
      //检查定时器中的所有事件
      void ngx_event_expire_timer(void);
      c
      //从定时器中移除一个事件
      static ngx_inline void ngx_event_del_timer(ngx_event_t *ev);
      
      //添加一个事件到定时器中
      static ngx_inline void ngx_event_add_timer(ngx_event_t *ev, ngx_msec_t timer);
      
  • nginx使用的时间是缓存在其内存中,获取时间时只需获取内存中几个整形变量即可
  • nginx启动时会更新一次时间
  • 后续时间通过 ngx_epoll_process_events 调用ngx_time_update更新
  • 缓存的时间精度 :timer_resolution

11. filter过滤器模块

  • 作用:对发送给用户的http响应包做一些加工

  • 区别:过滤模块与其他处理模块的区别在于过滤模块不会去访问第三方服务

  • 过滤器模块接口:

    • 发送http头部:
      • ngx_http_send_header
      • ngx_http_top_header_filter
      • ngx_http_next_header_filter
    • 发送http包体:
      • ngx_http_output_filter
      • ngx_http_top_body_filter
      • ngx_http_next_body_filter
  • 过滤器链表的顺序

    • 过滤模块间的调用顺序非常重要
    • 在ngx_modules.c数组中的位置越靠后,越优先执行
    • 可以自行修改 ngx_modules.c中过滤模块的顺序
  • 过滤器模块的开发步骤

    • 确定源代码文件名称
    • 在源代码目录下创建config脚本文件,其中过滤模块表示为HTTP_FILTER_MODULES
    • 定义过滤模块
    • 处理感兴趣的配置项
    • 实现初始化方法:将ngx_http_output_headrer_filter_pt和ngx_http_output_body_filter_pt插入过滤模块链表首部
    • 实现处理http头部的方法,即实现ngx_http_output_headrer_filter_pt原型方法
    • 实现处理http包体的方法,即实现ngx_http_output_body_filter_pt原型方法
    • 编译安装

12. upstream 代理转发模块

12.1 upstream & subrequest

  • nginx提供了两种全异步方式来与第三方服务器通信
    • upstream:能保证在与第三方服务器交互时不会阻塞Nginx进程处理其他请求
      • 希望将第三方服务的内容原封不动的返回给用户时,使用upstream
      • uptsream提供了三种处理上游服务器包体的方式,分别为交由http模块使用input_filter回调方法直接处理包体,以固定缓冲区转发包体,以多个缓冲加磁盘文件的方式转发包体
        • 当请求的ngx_http_request_t中的subrequest_in_memory为1时,则upstream不转发响应包体到下游,有http模块实现的input_filter方法处理包体
        • 当sub_request_in_memory为0,且ngx_http_upstream_conf_t中buffering为1时,则以多个缓冲加磁盘文件的方式转发包体,意味着上游网速快
        • 当buffering为0 时,则以固定缓冲区转发包体
      • upstream三个必须要实现的回调方法:
        • create_request: 构建发送给上游服务器的http请求
        • process_header:负责解析上游服务器发来的基于TCP的包头
        • finalize_request:结束请求,释放资源
      • 回调函数;
        • create_request
        • reinit_request:会被多次回调,产生的原因是与上游服务器建立连接失败
        • finalize_request:请求被销毁前会调用
        • process_header:会被多次调用,用于解析上游服务器返回的响应头部
        • rewrite_redirect:重定向
        • input_filter_init & input_filter:处理上游服务器的响应包体
      • 启动upstream机制
        • 执行ngx_http_upstream_init方法启动
    • subrequest:是由http框架提供一种分解复杂请求的设计模式
      • 把原始请求分解成多个子请求,使得诸多子请求协同完成一个用户请求
      • 本质上与第三方服务没有关系
      • 如果访问第三服务只是为了获取某些信息,在依据这些信息来构造响应并发送给用户,这时应该使用subrequest
      • 操作步骤:
        • 在nginx.conf文件中配置好子请求的处理方式
        • 实现子请求执行结束时的回调方法
          • nginx在子请求正常或异常结束时,都会调用ngx_http_post_subrequest_pt回调方法
        • 实现父请求被激活时的回调方法:对应于ngx_http_event_hander_pt,这个方法负责发送响应包给用户
        • 在上述的回调方法中启动subrequest子请求:调用ngx_http_subrequest建立子请求
      • 使用场景
        • 如何启动subrequest
          • 在父请求返回NGX_DONE后会开始执行子请求
        • 如何转发多个子请求的响应包
          • postpone 模块使用ngx_http_postpone_filter 方法将带转发的包体以合适的顺序在进行整理发送到下游客户端
        • 子请求如何激活父请求
          • 子请求在结束前会回调ngx_http_subrequest_t中实现的handler方法,在该方法中有设置了父请求被激活后的执行函数

12.2 upstream 机制的设计与实现

  • 核心思想
    • upstream 机制是事件驱动框架和HTTP框架的综合
    • upstream机制既提供基本的与上游服务器交互的功能外,还实现了转发上游应用层协议的响应包体到下游客户端的功能
    • 转发响应需要解决的问题
      • 上下游协议不一致,如下游是http,上游是tcp
      • 上下游网速差别大
  • 核心结构
    • ngx_http_stream_t
    • ngx_http_stream_conf_t
  • 核心流程
    • 启动upstream
      • 调用 ngx_http_upstream_create 从内存池中创建 ngx_http_upstream_t 结构体
      • 调用 ngx_http_upstream_init 会根据 ngx_http_upstream_conf_t 初始化 ngx_http_upstream_t中的成员并启动upstream机制
        • 检查下游读事件的timer_set标志位,若为1则将读事件从定时器中移除 和 ignore_client_abort 配置
        • 设置检查nginx与下游客户端的连接状态方法
        • 调用http模块实现的create_request方法,构造发往上游服务器的请求
        • 将ngx_http_upstream_cleanup 方法添加到cleanup链表
        • 调用ngx_http_upsteam_connect 连接上游服务器
    • 与上游服务器建立连接
      • upstream机制与上游服务器是通过TCP连接的,因此先建立一个socket,并且设置为非阻塞模式
      • 从空闲连接池free_connections中获取一个 ngx_connection_t
      • 将socket加入epoll重进行监控读写事件
      • 调用 ngx_http_upstream_connect 连接服务器
      • 将读写事件的回调函数设置为ngx_http_upstream_handler
      • 将upstream机制的 write_event_handler设置为ngx_http_upstream_send_request_handler(向上游服务器发送请求)
      • 将upstream机制的 read_event_handler设置为ngx_http_upstream_process_header(接收上游服务器响应)
      • 检查ngx_http_upstream_connect的返回值
        • 若失败,则将socket重新加入epoll中监听,如果它出现可写事件,就说明连接建立成功
        • 若成功,则调用 ngx_http_upstreamm_send_request 发送请求给上游服务器
    • 发送请求到上游服务器
      • 请求大小未知,故需要多次调用epoll才能将请求发完
      • 核心接口
        • ngx_http_upstream_send_request_handler
        • ngx_http_upstream_send_request 真正执行发送请求的接口
    • 接收上游服务器的响应头部
      • 响应数据 分为 包头 和 包体
        • 包头相当把不同协议包间的共同部分抽象出来
      • 处理包体的三种方式:
        • 不转发响应(即不实现反向代理)
        • 转发响应时以下游网速优先
        • 转发响应时以上游网速优先
      • 如何识别处理包体的方式
        • 当ngx_http_request_t中subrequest_in_memory为1,则不转发响应
        • 当ngx_http_request_t中subrequest_in_memory为0,则转发响应
          • 当ngx_http_upstream_conf_t中的buffering为0,则以下游网速优先,即用固定内存缓存
          • 当ngx_http_upstream_conf_t中的buffering为1,则以上游网速优先,即用更多内存及硬盘文件缓存
      • 核心接口
        • ngx_http_upstream_process_header 接收和解析响应头部
        • ngx_http_upstream_send_response 转发包体
      • 如果来自客户端的请求知己使用upstream机制,那都需要将上游服务器的响应直接转发给客户端
      • 如果是客户端请求派生出的子请求,则不需要转发上游的响应
    • 不转发响应时的处理流程
      • 客户端的子请求,一般使用该方式处理数据
      • 接收包体方法: ngx_http_upstream_process_body_in_memory
      • 处理包体方法: input_filter, 具体由个http模块自己实现,默认为ngx_http_upstream_non_buffered_filter
    • 以下游网速优先来转发响应
      • 以下游网速优先实际上只是意味着需要开辟一段固定长度的内存作为缓冲区
      • 转发响应包头方法:ngx_hhtp_upstream_send_response
      • 读取上游服务器响应的方法
        • ngx_http_upstream_process_non_buffered_upstream
          • ngx_http_upstream_process_non_buffered_request
      • 向下游客户端发送包体方法
        • ngx_http_upstream_non_buffered_downstream
          • ngx_http_upstream_process_non_buffered_request
    • 以上游网速优先来转发响应
      • 将ngx_http_upstream_conf_t 结构体中的buffering标志位设置为1,允许upstream机制打开更大的缓冲区缓存那些来不及向下游客户端转发的响应

      • 核心结构体 :ngx_event_pipe_t

      • ngx_event_pipe_read_upstream 方法将会把接收到的响应存放到内存或者磁盘文件中,同时用ngx_buf_t缓冲区指向这些响应,最后用in和out缓冲区链表把这些ngx_buf_t缓冲区管理起来

      • ngx_event_pipe_write_to_downstream 负责把 in链表和out链表管理的缓冲区发送给下游客户端

    • 结束upstream请求
      • 核心接口:
        • ngx_http_upstream_finalize_request
          • ngx_http_finalize_request
        • ngx_http_upstream_cleanup
        • ngx_http_upstream_next

13. HTTP框架初始化

13.1 模块

  • 分类

    • 1个核心模块
      • ngx_http_module
    • 2个http模块
      • ngx_http_core_module
      • ngx_http_upstream_module
  • 管理:

    • 每个HTTP模块都需要实现 ngx_http_module_t 接口
      typedef struct ngx_http_module_t {
          //在解析http{...}配置项前回调
          ngx_int_t (*preconfiguration)(...);
          //解析完http{...}内的所有配置项后回调
          ngx_int_t (*postconfiguration)(...);
          void *(*create_main_conf)(...);
          char *(*init_main_conf)(...);
          void *(*create_srv_conf)(...);
          char *(*merge_srv_conf)(...);
          void *(*create_loc_conf)(...);
          char *(*merge_sr_conf)(...);    
      }
      

13.2 配置项

  • 分类:

    • 隶属于http{}块内的配置项为main配置项
    • 隶属于server{}块内的配置项为srv配置项
    • 隶属于location{}块内的配置项为loc配置项
  • 分级管理:

    • 每个级别的配置项都有属于该级别配置块的ngx_http_conf_ctx_t
    struct ngx_http_conf_ctx_t
    {
        //指向由create_main_conf生成的ngx_http_core_main_conf_t
        void **main_conf;
        //指向由create_srv_conf生成的ngx_http_core_srv_conf_t
        void **srv_conf;
        //指向由create_loc_conf生成的ngx_http_core_loc_conf_t
        void **loc_conf;
    }
    
    • main 级别
      • 获取main级别的配置 ngx_http_cycle_get_module_main_conf(…);
    • srv 级别
      • main_conf 指向所属HTTP块下ngx_http_conf_ctx_t下的main_conf
      • srv_conf 和loc_conf重新分配数组,大小为ngx_http_max_module
      • srv块的配置ngx_http_conf_ctx_t结构体都是有ngx_array_t动态数组组织起来的
    • loc 级别
      • main_conf 指向所属srv块下ngx_http_conf_ctx_t下的main_conf
      • srv_conf 指向所属srv块下ngx_http_conf_ctx_t下的srv_conf
      • loc_conf重新分配数组,大小为ngx_http_max_module
      • 若location中有正则表达式,提高性能
      • 同一srv块的ngx_http_core_loc_conf_t是通过双向链表串联起来的
      • location配置块允许嵌套
      • ngx_http_merge_location 合并location级别的配置项
  • 不同级别合并

    • 方法:
      • merge_srv_conf:合并main与srv级别的server相关配置项
      • merge_loc_conf :合并main、srv、loc级别的location相关配置项

13.3 监听端口管理

  • 一个http 配置项下可以有多个监听端口,保存在ngx_http_core_main_conf_t下的ports成员
  • 每监听一个端口都使用 ngx_http_conf_port_t结构体来表示
  • 一个ngx_http_conf_port_t将对应着多个ngx_http_conf_addr_t
  • ngx_http_conf_addr_t是以动态数组的形式保存在ngx_http_conf_port_t的addrs成员中

13.4 server的快速检索

  • 使用散列表实现
  • 在ngx_http_conf_addr_t 有三个散列表成员 hash,wc_head,wc_tail

13.5 location的快速检索

  • 使用静态二叉树实现
  • ngx_http_init_static_location_tree 构建location配置项的静态二叉树
  • 调用ngx_http_core_find_location 可以从静态二叉查找树中快速检索到ngx_http_core_loc_conf_t

13.6 http请求的11个阶段

  • NGX_HTTP_POST_READ_PHASE

    • 在接收到完整的HTTP头部后处理的HTTP阶段,主要是获取客户端真实IP
    • 该阶段handler方法:ngx_http_realip_handler
  • NGX_HTTP_SERVER_REWRITE_PHASE

    • 在还没有查询到URI匹配的location前,这时rewrite重写URL
    • 作为一个独立的HTTP阶段 server配置项内请求地址重写阶段
    • 该阶段handler方法:ngx_http_rewrite_module
  • NGX_HTTP_FIND_CONFIG_PHASE

    • 根据URI寻找匹配的location
    • 该阶段只能由ngx_http_core_module模块处理,不允许用户添加hander方法
  • NGX_HTTP_REWRITE_PHASE

    • 在NGX_HTTP_FIND_CONFIG_PHASE阶段寻找到匹配的location之后再修改请求的URI
    • 在NGX_HTTP_FIND_CONFIG_PHASE阶段之后重写URL的意义与NGX_HTTP_SERVER_REWRITE_PHASE阶段是不同的,因为这两者会导致查找到不同的location块(location是与URI进行匹配的)
    • 这一阶段是用于在rewrite URI后重新跳到NGX_HTTP_FIND_CONFIG_PHASE阶段找到与新的URI匹配的location
    • 该阶段只能由ngx_http_core_module模块处理
    • 该阶段handler方法:ngx_http_rewrite_handler
  • NGX_HTTP_POST_REWRITE_PHASE

    • 该阶段用于在rewrite URL后,防止错误的nginx.conf 配置导致死循环(递归地修改URI)
    • 控制死循环的方式:首先检查rewrite的次数,如果一个请求超过10次重定向,则认为进入rewrite死循环,这时在NGX_HTTP_POST_REWRITE_PHASE阶段就会向用户返回500表示服务器内部错误
    • 该阶段只能由ngx_http_core_module模块处理,不允许用户添加hander方法
  • NGX_HTTP_PRE_ACCESS_PHASE

    • 访问权限检查的前期工作
    • 该阶段handler方法:
      • ngx_http_degradation_handler
      • ngx_http_limit_conn_handler
      • ngx_http_limit_req_handler
      • ngx_http_realip_handler
  • NGX_HTTP_ACCESS_PHASE

    • 访问权限检查的中期工作
    • HTTP模块判断是否允许这个请求访问Nginx服务器
    • 该阶段handler方法:
      • ngx_http_access_handler
      • ngx_http_auth_basic_handler
      • ngx_http_auth_request_handler
  • NGX_HTTP_POST_ACCESS_PHASE

    • 访问权限检查的后期工作
    • 该阶段实际上用于给NGX_HTTP_ACCESS_PHASE阶段收尾
    • 当NGX_HTTP_ACCESS_PHASE阶段中HTTP模块的handler处理方法返回不允许访问的错误码时(实际是NGX_HTTP_FORBIDDEN 或者 NGX_HTTP_UNAUTHORIZED)这个阶段将负责构造拒绝服务的用户响应
    • 该阶段只能由ngx_http_core_module模块处理,不允许用户添加hander方法
  • NGX_HTTP_TRY_FILES_PHASE

    • 该阶段完为了try_files配置项而设立的
    • 当HTTP请求访问静态文件资源时,try_files配置项可以使这个请求顺序地访问多个静态文件资源
    • 如果某一次访问失败,则继续访问try_files中指定的下一个静态资源
    • 该阶段只能由ngx_http_core_module模块处理,不允许用户添加hander方法
  • NGX_HTTP_CONTENT_PHASE

    • 用于处理HTTP请求内容的阶段,这是大部分HTTP模块最喜欢介入的阶段
    • 其余10个阶段中各HTTP模块的处理方法都是放在全局的
      ngx_http_core_main_conf_t结构体中的,也就是说它们对任何
      一个HTTP请求都是有效的
    • ngx_http_handler_pt处理方法不再应用于所有的HTTP请求,仅仅当用户请求的URI匹配了location时才会被调用,这也就意味着它是一种完全不同于其他阶段的使用方式
    • 当HTTP模块实现了某个ngx_http_handler_pt处理方法并希望介入NGX_HTTP_CONTENT_PHASE阶段来处理用户请求时,如果希望这个ngx_http_handler_pt方法应用于所有的用户请求,则应该在ngx_http_module_t接口的postconfiguration方法中,向ngx_http_core_main_conf_t结构的phases[NGX_HTTP_CONTENT_PHASE]动态数组中添加ngx_http_handler_pt处理方法;
    • 反之,如果希望这个方式仅应用于URI匹配某些location的用户请求,则应该在一个location下配置项的回调方法中,把ngx_http_handler_pt方法设置到ngx_http_core_loc_conf_t结构体的handler中
    • 但NGX_HTTP_CONTENT_PHASE阶段仅仅针对某种请求唯一生效
    • 该阶段handler方法:
      • ngx_http_autoindex_handler
      • ngx_http_dav_handler
      • ngx_http_gzip_static_handler
      • ngx_http_index_handler
      • ngx_http_random_index_handler
      • ngx_http_static_handler
  • NGX_HTTP_LOG_PHASE

    • 处理完请求后记录日志的阶段
    • 该阶段handler方法:ngx_http_log_handler
  • 总结

    • ngx_http_phase_engine_t 保存了在nginx.conf配置下,一个用户请求可能经历的所有ngx_http_handler_pt处理方法,这是所有http模块合作处理用户请求的关键
    • 所有阶段都有 checker 和 handler函数
    • 各个阶段的http框架check函数见 ngx_http_init_phase_handlers
    • 所有阶段的checker在ngx_http_core_run_phases中调用
    • 有7个阶段支持第三方HTTP模块实现自己的ngx_http_handler_pt处理方法,分别如下:
    序号 阶段名
    1 NGX_HTTP_POST_READ_PHASE
    2 NGX_HTTP_SERVER_REWRITE_PHASE
    3 NGX_HTTP_HTTP_REWRITE_PHASE
    4 NGX_HTTP_HTTP_PREACCESS_PHASE
    5 NGX_HTTP_HTTP_ACCESS_PHASE
    6 NGX_HTTP_CONTENT_PHASE
    7 NGX_HTTP_LOG_PHASE

14. HTTP 框架执行流程

  • 大体流程

    • 与客户端建立TCP连接
    • 接收HTTP请求行、头部并解析出它们的意义
    • 根据nginx.conf配置文件找到HTTP模块,并依次合作处理该请求
  • 核心结构

    • 连接 ngx_connection_t
    • 事件 ngx_event_t
  • 细分流程

    • 新建立连接

      • 核心接口:ngx_http_init_connection
    • 第一次可读事件

      • 核心接口:ngx_http_init_request
    • 接收http请求行

      • 核心接口:ngx_http_process_request_line
    • 接收http头部

      • 核心接口:ngx_http_process_request_headers
      • 作用:接收当前请求全部的http头部,可能会被多次调用
      • http请求行和头部的总长度不能超过 large_client_header_buffers指定的字节
    • 处理http请求

      • 核心接口:
        • ngx_http_process_request
          • 作用:负责在接收完HTTP头部后,第一次与各个http模块共同按阶段处理请求(即首次从业务上处理请求)
        • ngx_http_request_handler
          • http请求上读/写事件的回调函数
          • 作用:如果ngx_http_process_request没有处理完请求,这个请求上的事件再次被触发,那么就由此方法继续处理(TCP连接上后续的事件触发)
      • 两个接口的共同点:都会先按阶段调用各个http模块处理请求,再处理post请求
        • 各阶段处理请求:就是通过每个阶段的checker方法来实现
          • 四个主要的checker方法
            • ngx_http_core_generic_phase
              • 使用该方法的3个阶段
                • NGX_HTTP_POST_READ_PHASE
                • NGX_HTTP_PREACCESS_PHASE
                • NGX_HTTP_LOG_PHASE
            • ngx_http_core_rewrite_phase
              • 使用该方法的2个阶段
                • NGX_HTTP_SERVER_REWRITE_PHASE
                • NGX_HTTP_REWRITE_PHASE
              • 与ngx_http_core_generic_phase的不同点
                • ngx_http_core_rewrite_phase不存在跳到下一个阶段的处理请求
            • ngx_http_core_access_phase
              • 使用该方法的1个阶段
                • NGX_HTTP_ACCESS_PHASE 用于控制用户发起的请求是否合理
              • 配置文件中的satisfy参数
                • all:此阶段中所有handler方法全部返回ngx_ok才认为该请求具访问权限
                • any:此阶段中任一handler方法返回ngx_ok则认为该请求具访问权限
            • ngx_http_core_content_phase
              • 使用该方法的1个阶段
                • NGX_HTTP_CONTENT_PHASE
                  • http模块开发最常用的一个阶段
                  • 作用:真正用于处理请求的内容
    • subrequest与post请求

      • subrequest机制特点:
        • 从业务上将一个复杂请求拆分成多个子请求,由这些子请求共同合作完成实际请求
        • 每一个http模块通常只需关心一个请求,这极大降低模块的开发复杂度
      • post请求的设计就是用于实现subrequest子请求机制的
      • 子请求不是被网络事件驱动的,因此执行post请求时相当于有可写事件,由nginx主动做出的动作
    • 处理http包体

      • 在http中,一个请求通常由必选的http请求行,请求头以及可选的包体组成
      • 接收完http头部后就开始调用各HTTP模块处理请求,然后由http模块决定如何处理包体
      • http框架处理http包体的方式:
        • 把请求中的包体接收到内存或文件中
        • 选择接收完包体后直接丢弃
      • 引用计数count
        • 一般作用于当前请求的原始请求上,在结束请求时统一检查原始请求的引用计数即可
      • 接收包体:
        • 核心结构:
          • ngx_http_request_body_t
        • 核心接口:
          • ngx_http_read_client_request_body
          • ngx_http_read_client_request_body_handler
          • ngx_http_do_read_client_request_body
      • 放弃接收包体
        • 核心接口:
          • ngx_http_discard_request_body 第一次启动丢弃包体动作
          • ngx_http_discard_request_body_handler 有新的可读事件会调用它处理包体
          • ngx_http_read_discard_request_body 上述两者的公共方法,用来读取包体且不作任何处理
    • 发送http响应

      • 核心接口
        • 发送http响应行、头:ngx_http_send_header
          • ngx_http_top_header_filter
          • ngx_http_header_filter
        • 发送http响应包体:ngx_http_output_filter
        • 内部接口,后台执行:ngx_http_write_filter
      • 无论是ngx_http_send_header 还是 ngx_http_output_filter方法,调用时都无法发送全部的响应,剩下的响应内容都得靠 ngx_http_writer方法发送
    • 结束http请求

      • 核心接口:ngx_http_finalize_request
  • 代码流程:(ngx_http_request.c

    ngx_string("http") --> ngx_xxx
        |
    ngx_http_optimize_servers
        |
    ngx_http_listenting 
        |
    ngx_http_add_listening  //执行命令行,解析配置文件后就开始执行
        					//不管有无连接,该函数都会调用 
        |
    ngx_http_init_connection  //当epoll_wait触发产生连接后会调用
        |
    ngx_http_wait_request_handler  // http接收数据的开始
        |
    recv 接收数据
        |
    ngx_http_parse_request_line
        |
    ngx_http_process_request_line
        |
    ngx_http_process_request_header
        |
    ngx_http_process_request
        |
    ngx_http_init_phases
        |
    ngx_http_init_phase_handlers
        |
    ngx_htto_core_run_phases
    

15. 如何开发一个http模块

  • 开发思路:

    • 当我们开发HTTP模块实现某个功能时,若需要访问上游服务器获取一些数据,那么可开发两个HTTP模块
      • 第一个HTTP模块专用于处理客户端请求,当它需要访问上游服务器时就派生出子请求访问
      • 第二个HTTP模块专用于访问上游服务器,在子请求解析完上游服务器的响应后,再激活父请求处理客户端要求的业务
  • 命名样式: ngx_http_xxx_module

  • 重点数据结构:

    • ngx_list_t: nginx封装的链表容器 ---- 存储数组元素的链表
    • ngx_table_elt_t: key/value对,key存储http头部字段名称,value存储对应的值
    • ngx_buf_t: 处理大数据的关键数据结构
  • 第三方模块编译进nginx

    • 方法:在configure脚本执行时加入参数 “ --add-module=PATH” , PATH为源码和config文件的存放路径 (源码和config文件放在同一目录)

    • config文件写法:

      • ngx_addon_name:设置为模块名称
      • HTTP_MOUDLES : 保存所有模块的名称,每个模块间用空格相连(如果开发过滤器模块,用HTTP_FILTER_MODULES代替)
      • NGX_ADDON_SRC :用于指定新增模块的源码,多个源码文件用空格相连,在设置NGX_ADDON_sRCS是可以使用$ngx_addon_dir,它相当于–add-module=PATH的PATH参数
      • NGX_ADDON_DEPS : 指定了模块的依赖路径
    • 示例

      ngx_addon_name=ngx_http_count_moudle  	
      HTTP_MODULES="$HTTP_MODULES  ngx_http_count_module"
      NGX_ADDON_SRC="$NGX_ADDON_SRC $ngx_addon_dir/ngx_http_count_module.c"
      
  • configure脚本如何将模块加入nginx

    • 方法:–add-module=PATH, PATH表示第三方模块所在的路径
    • . auto/modules脚本第三方模块加入ngx_modules.c
    • . auto/make 编译

16. nginx进程间通信机制

  • nginx进程间使用的通信机制
    • 共享内存
    • 套接字
    • 信号:传递消息
    • nginx频道
      • master进程和worker进程间常用的通信工具
      • 使用本机套接字实现的
  • 多进程访问共享资源机制
    • 原子操作

      • 能够执行原子操作的只有整型
      • 分类:
        • 不支持原子库下的原子操作
          • ngx_atomic_cmp_set
          • ngx_atomic_fetch_add
        • x86架构下的原子锁
        • 自旋锁spin_lock
          • 自旋锁是一种非睡眠锁,某进程如果试图获得自旋锁,当发现锁已经被其他进程获得时,那不会使得当前进程进入睡眠状态,而是始终保持运行状态,每当内核调度到这个进程时就会检查是否可以获取到锁
          • 自旋锁主要是为多处理器操作系统设置的
          • 使用的场景是进程使用锁的时间非常短
            • 如果锁的使用时间长,自旋锁就不合适了,因为它会占用大量CPU资源
    • 信号量

      • 作用:一种保证共享资源有序访问的工具
      • 使用信号量作为互斥锁有可能导致进程睡眠
    • 文件锁

      • 作用:使用文件互斥锁来保护共享数据集
      • nginx.conf中的lock_file指定的文件路径就是用于文件互斥锁的,该文件被打开后的文件句柄作为fd传递fcntl,提供一个文件锁机制
    • 互斥锁

      • nginx基于原子操作、信号量、文件锁封装的
      • 文件锁是实现的ngx_shmtx_t
      • 原子变量实现的ngx_shmtx_t
      • 原子变量锁的优先级高于文件锁

17. nginx服务器代理模型

17.1 nginx代理

  • 定义:nginx 能够代理和它同局域网内的其他服务器,起到一个应用层网关的作用

  • 原理:

    • 在路由器端通过端口映射NAT,将接收到的外网客户端访问请求统一转发给nginx,再由nginx分发给网段内的其他服务器,从而起到负载均衡的作用
    • 内网下的服务器集群是不直接对外提供服务的,所有对外的收发数据服务统一由nginx代理
  • 原理图
    学习Nginx看这篇就够了

学习Nginx看这篇就够了

17.2 代理与跳转

  • 代理
    学习Nginx看这篇就够了

    学习Nginx看这篇就够了

  • 跳转
    学习Nginx看这篇就够了

  • nginx与squid代理服务器对比:

    • nginx收到http请求后不会立马转发给客户端,而是将数据缓存起来,待请求数据接收完成后再统一发给上游服务器
    • squid则是边接收边转发

    学习Nginx看这篇就够了

  • nginx代理方式的优缺点:
    优点:降低上游服务器的负载
    缺点:延长了一个请求的处理时间,并增加了用于缓存请求内容的内存和磁盘空间

18. CGI

  • 定义:公共网关接口 Common Gateway Interface
  • 作用:cgi对外提供输入输出流,如stdin/stdout
  • 使用场景:在线编程(牛客,力扣)
  • cgi 与 fastcgi的区别
    • cgi :一请求一进程
    • fastcgi : 先创建一个进程池,来一个请求分配一个进程
  • spwanfcgi:用来启动fcgi的
  • nginx与cgi的交互流程
    学习Nginx看这篇就够了

学习Nginx看这篇就够了

19. http 请求大文件问题

  • http头部重要字段: range:0 - 1024
  • 方法:
    • 开启多线程同时下载, 每个线程请求带的range不同,分别下载源文件的不同位置
    • 然后多线程同时写文件,由于硬盘只有磁头,所以写文件时,只能是串行写入,但磁盘的读写速度要比网络读写速度快得多,所以在用户看来就像是多线程并行写入文件
上一篇:不懂运维的开发人员能走多远?快来学习一下 Nginx 的配置吧!


下一篇:编译nginx时openssl报错的解决方案