本文主要是介绍学习Nginx看这篇就够了,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
0. NGINX的优点
- 响应速度快
- 高扩展性
- 高可靠性
- 每个worker进程相对独立
- master进程在一个worker进程挂掉后,会快速启动新worker进程提供服务
- 低内存消耗
- 1w个非活跃的http keepalive连接仅消耗2.5M的内存
- 单机支持10W+的并发连接
- 支持热部署
- master管理进程与worker进程分离设计,使得nginx能够支持热部署功能
- 支持free bsd
- 总的一句,nginx能在支持高并发请求的同时保持高效的服务
1. 配置编译安装
1.1 配置
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 负载均衡配置
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/;
}
}
}
- 配置文件处理源码流程:
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 红黑树
- 支持通配符的散列表:检索和插入速度的期望时间均为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,
...
}
-
核心结构体:
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);
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地址,只需要配置好,其他不用管
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 主动连接
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过滤器模块
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
- 如何转发多个子请求的响应包
- 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个核心模块
- 2个http模块
- ngx_http_core_module
- ngx_http_upstream_module
-
管理:
13.2 配置项
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_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模块
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代理
17.2 代理与跳转
18. CGI
- 定义:公共网关接口 Common Gateway Interface
- 作用:cgi对外提供输入输出流,如stdin/stdout
- 使用场景:在线编程(牛客,力扣)
- cgi 与 fastcgi的区别
- cgi :一请求一进程
- fastcgi : 先创建一个进程池,来一个请求分配一个进程
- spwanfcgi:用来启动fcgi的
- nginx与cgi的交互流程
19. http 请求大文件问题
- http头部重要字段: range:0 - 1024
- 方法:
- 开启多线程同时下载, 每个线程请求带的range不同,分别下载源文件的不同位置
- 然后多线程同时写文件,由于硬盘只有磁头,所以写文件时,只能是串行写入,但磁盘的读写速度要比网络读写速度快得多,所以在用户看来就像是多线程并行写入文件
这篇关于学习Nginx看这篇就够了的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!