900字范文,内容丰富有趣,生活中的好帮手!
900字范文 > 学习Nginx看这篇就够了

学习Nginx看这篇就够了

时间:2022-03-03 06:05:25

相关推荐

学习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 ;}

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 ;}

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 ;}

3.3 反向代理配置

proxy_pass URL: 将当前请求反向代理URL参数指定的服务器上 示例:proxy_pass http://192.168.0.1:8080/uri/;注意:默认情况下反向代理是不会转发请求中的host头部,如需转发则要加上:proxy_set_header Host $hostproxy_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_MODULENGX_CORE_MODULENGX_CONF_MODULENGX_EVENT_MODULENGX_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_modulehttp模块: ngx_http_modulemail模块: ngx_mail_module

6.2 事件驱动架构

简单说就是由一些事件发生源来产生事件,由一个或多个事件收集器来收集分发事件,然后许多事件处理器会注册自己感兴趣的事件,然后消费这些事件将对IO管理转换为事件管理不同操作系统,不同内核版本有不同的事件驱动机制分类:

6.3 请求多阶段异步处理

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

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

master进程:一个管理进程 master进程工作机制 master 进程不需要处理TCP网络事件,不负责业务的执行,只会负责管理worker子进程以实现重启服务,平滑升级,更换日志文件,配置文件实时生效等功能 操作系统通过信号管理master进程master进程信号定义:worker进程:多个工作子进程

如何启动子进程:ngx_spawn_process 内部封装了fork

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

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

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

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

worker进程信号定义

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_timersngx_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默认的工作模式,可以处理阻塞和非阻塞socketET 边缘触发 :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 主动连接

核心结构

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_headerngx_http_top_header_filterngx_http_next_header_filter 发送http包体: ngx_http_output_filterngx_http_top_body_filterngx_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进程处理其他请求 希望将第三方服务的内容原封不动的返回给用户时,使用upstreamuptsream提供了三种处理上游服务器包体的方式,分别为交由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_requestreinit_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_tngx_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_handlerngx_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_cleanupngx_http_upstream_next

13. HTTP框架初始化

13.1 模块

分类

1个核心模块 ngx_http_module 2个http模块 ngx_http_core_modulengx_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_tvoid **main_conf;//指向由create_srv_conf生成的ngx_http_core_srv_conf_tvoid **srv_conf;//指向由create_loc_conf生成的ngx_http_core_loc_conf_tvoid **loc_conf;}

main 级别 获取main级别的配置 ngx_http_cycle_get_module_main_conf(…); srv 级别 main_conf 指向所属HTTP块下ngx_http_conf_ctx_t下的main_confsrv_conf 和loc_conf重新分配数组,大小为ngx_http_max_modulesrv块的配置ngx_http_conf_ctx_t结构体都是有ngx_array_t动态数组组织起来的 loc 级别 main_conf 指向所属srv块下ngx_http_conf_ctx_t下的main_confsrv_conf 指向所属srv块下ngx_http_conf_ctx_t下的srv_confloc_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_tngx_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_handlerngx_http_limit_conn_handlerngx_http_limit_req_handlerngx_http_realip_handler

NGX_HTTP_ACCESS_PHASE

访问权限检查的中期工作HTTP模块判断是否允许这个请求访问Nginx服务器该阶段handler方法: ngx_http_access_handlerngx_http_auth_basic_handlerngx_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_handlerngx_http_dav_handlerngx_http_gzip_static_handlerngx_http_index_handlerngx_http_random_index_handlerngx_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处理方法,分别如下:

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_PHASENGX_HTTP_PREACCESS_PHASENGX_HTTP_LOG_PHASE ngx_http_core_rewrite_phase 使用该方法的2个阶段 NGX_HTTP_SERVER_REWRITE_PHASENGX_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_bodyngx_http_read_client_request_body_handlerngx_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_filterngx_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_setngx_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代理

原理图

17.2 代理与跳转

代理

跳转

nginx与squid代理服务器对比:

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

nginx代理方式的优缺点:

优点:降低上游服务器的负载

缺点:延长了一个请求的处理时间,并增加了用于缓存请求内容的内存和磁盘空间

18. CGI

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

19. http 请求大文件问题

http头部重要字段: range:0 - 1024方法: 开启多线程同时下载, 每个线程请求带的range不同,分别下载源文件的不同位置然后多线程同时写文件,由于硬盘只有磁头,所以写文件时,只能是串行写入,但磁盘的读写速度要比网络读写速度快得多,所以在用户看来就像是多线程并行写入文件

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。