Nginx学习笔记(三)

过滤模块简介

执行时间和内容

过滤(filter)模块是过滤响应头和内容的模块,可以对回复的头和内容进行处理。它的处理时间在获取回复内容之后,向用户发送响应之前。它的处理过程分为两个阶段,过滤 HTTP 回复的头部和主体,在这两个阶段可以分别对头部和主体进行修改。

执行顺序

过滤模块的调用是有顺序的且在编译时决定。控制编译的脚本位于 auto/modules 中,当你编译完 Nginx 以后,可以在 objs 目录下面看到一个 ngx_modules.c 的文件。打开这个文件,有类似的代码:

ngx_module_t *ngx_modules[] = { ... &ngx_http_write_filter_module, &ngx_http_header_filter_module, &ngx_http_chunked_filter_module, &ngx_http_range_header_filter_module, &ngx_http_gzip_filter_module, &ngx_http_postpone_filter_module, &ngx_http_ssi_filter_module, &ngx_http_charset_filter_module, &ngx_http_userid_filter_module, &ngx_http_headers_filter_module, &ngx_http_copy_filter_module, &ngx_http_range_body_filter_module, &ngx_http_not_modified_filter_module, NULL };

从 write_filter 到 not_modified_filter,模块的执行顺序是反向的。即最早执行的是 not_modified_filter,然后各个模块依次执行。一般情况下,第三方过滤模块的 config 文件会将模块名追加到变量 HTTP_AUX_FILTER_MODULES 中,此时该模块只能加入到 copy_filter 和 headers_filter 模块之间执行。

模块编译

Nginx 可以加入第三方的过滤模块。在过滤模块的目录里,首先要加入 config 文件,文件的内容如下:

ngx_addon_name=ngx_http_example_filter_module HTTP_AUX_FILTER_MODULES="$HTTP_AUX_FILTER_MODULES ngx_http_example_filter_module" NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/ngx_http_example_filter_module.c"

把名为 ngx_http_example_filter_module 的过滤模块加入,ngx_http_example_filter_module.c 是该模块的源代码。

过滤模块的分析

相关结构体

ngx_chain_t 结构是一个单向链表:

typedef struct ngx_chain_s ngx_chain_t; struct ngx_chain_s { ngx_buf_t *buf; ngx_chain_t *next; };

在过滤模块中,所有输出的内容都是通过一条单向链表所组成。单向链表的设计好处是简单,非阻塞,但是跨链表的内容操作非常麻烦,如果需要跨链表,很多时候只能缓存链表的内容。

单链表负载的就是 ngx_buf_t,该结构体使用非常广泛,该结构体的代码如下:

一般 buffer 结构体可以表示一块内存,内存的起始和结束地址分别用 start 和 end 表示,pos 和 last 表示实际的内容。如果内容已经处理过了,pos 的位置就可以往后移动。如果读取到新的内容,last 的位置就会往后移动。所以 buffer 可以在多次调用过程中使用。如果 last 等于 end,就说明这块内存已经用完了。如果 pos 等于 last,说明内存已经处理完了。下面是一个简单的示意图,说明 buffer 中指针的用法:

响应头过滤函数

该函数主要用处是处理 HTTP 响应的头,可根据实际情况对于响应头进行修改、添加、删除。响应头过滤函数先于响应体过滤函数,且只调用一次,所以一般可作过滤模块的初始化工作。

响应头过滤函数的入口只有一个:

ngx_int_t ngx_http_send_header(ngx_http_request_t *r){ ... return ngx_http_top_header_filter(r); }

该函数向客户端发送回复的时候调用。该函数的返回值一般是 NGX_OK,NGX_ERROR 和 NGX_AGAIN,分别表示处理成功,失败和未完成。

响应体过滤函数

该函数是过滤响应主体的函数。ngx_http_top_body_filter 这个函数每个请求可能会被执行多次,它的入口函数是 ngx_http_output_filter,如:

具体模块的响应体过滤函数的格式类似如下:

static int ngx_http_example_body_filter(ngx_http_request_t *r, ngx_chain_t *in){ ... return ngx_http_next_body_filter(r, in); }

子请求

Nginx 过滤模块可以发出子请求,即在过滤响应内容的时候,可以发送新的请求,Nginx 会根据调用的先后顺序,将多个回复的内容拼接成正常的响应主体。

为保证父请求和子请求的顺序,当 Nginx 发出子请求时,就会调用 ngx_http_subrequest 函数,将子请求插入父请求的 r->postponed 链表中。子请求会在主请求执行完毕时获得依次调用。子请求同样会有一个请求所有的生存期和处理过程,也会进入过滤模块流程。

关键点在 postpone_filter 模块中,它会拼接主请求和子请求的响应内容。r->postponed 按次序保存父请求和子请求,它是一个链表,如果前一个请求未完成,那后一个请求内容就不会输出。当前一个请求完成时并输出时,后一个请求才可输出,当所有的子请求都完成时,所有的响应内容也就输出完毕了。

upstream 模块

upstream模块简介

模块简介

upstream 模块使 Nginx 跨越单机的限制,完成网络数据的接收、处理和转发。数据转发功能,为 Nginx 提供了跨越单机的横向处理能力,使 Nginx 摆脱只能为终端节点提供单一功能的限制,而使它具备了网路应用级别的拆分、封装和整合的战略功能。

模块接口

upstream 模块需要开发若干回调函数,完成构造请求和解析响应等具体的工作。

这些回调函数如下表所示:

SN描述
create_request生成发送到后端服务器的请求缓冲(缓冲链),在初始化 upstream 时使用。
reinit_request在某台后端服务器出错的情况,Nginx会尝试另一台后端服务器。Nginx 选定新的服务器以后,会先调用此函数,以重新初始化 upstream 模块的工作状态,然后再次进行 upstream 连接。
process_header处理后端服务器返回的信息头部。所谓头部是与 upstreamserver 通信的协议规定的,比如 HTTP 协议的 header 部分,或者 memcached 协议的响应状态部分。
abort_request在客户端放弃请求时被调用。不需要在函数中实现关闭后端服务器连接的功能,系统会自动完成关闭连接的步骤,所以一般此函数不会进行任何具体工作。
finalize_request正常完成与后端服务器的请求后调用该函数,与 abort_request 相同,一般也不会进行任何具体工作。
input_filter处理后端服务器返回的响应正文。Nginx 默认的 input_filter 会将收到的内容封装成为缓冲区链 ngx_chain。该链由 upstream 的 out_bufs 指针域定位,所以开发人员可以在模块以外通过该指针 得到后端服务器返回的正文数据。memcached 模块实现了自己的 input_filter,在后面会具体分析这个模块。
input_filter_init初始化 input filter 的上下文。Nginx 默认的 input_filter_init 直接返回。

memcached 模块

memcache 是一款高性能的分布式 cache 系统,memcache 定义了一套私有通信协议,使得不能通过 HTTP 请求来访问 memcache。

Nginx 提供了 ngx_http_memcached 模块,提供从 memcache 读取数据的功能,而不提供向 memcache 写数据的功能。

ngx_http_memcached_handler固定的操作流程如下:

1.创建 upstream 数据结构。

if (ngx_http_upstream_create(r) != NGX_OK) { return NGX_HTTP_INTERNAL_SERVER_ERROR; }

2.设置模块的 tag 和 schema。schema 现在只会用于日志,tag 会用于 buf_chain 管理。

u = r->upstream; ngx_str_set(&u->schema, "memcached://"); u->output.tag = (ngx_buf_tag_t) &ngx_http_memcached_module;

3.设置 upstream 的后端服务器列表数据结构。

mlcf = ngx_http_get_module_loc_conf(r, ngx_http_memcached_module); u->conf = &mlcf->upstream;

4.设置 upstream 回调函数。在这里列出的代码稍稍调整了代码顺序。

5.创建并设置 upstream 环境数据结构。

6.完成 upstream 初始化并进行收尾工作。

r->main->count++; ngx_http_upstream_init(r); return NGX_DONE;

任何 upstream 模块,简单如 memcached,复杂如 proxy、fastcgi 都是如此。不同的 upstream 模块在这 6 步中的最大差别会出现在第 2、3、4、5 上。其中第 2、4 两步很容易理解,不同的模块设置的标志和使用的回调函数肯定不同。第 5 步也不难理解,只有第3步是最为晦涩的,不同的模块在取得后端服务器列表时,策略的差异非常大。第 6 步是一个常态。将 count 加 1,然后返回 NGX_DONE。

回调函数

  • ngx_http_memcached_create_request:按照设置的内容生成一个 key,接着生成一个“get $key”的请求,放在 r->upstream->request_bufs 里面。
  • ngx_http_memcached_reinit_request:无需初始化。
  • ngx_http_memcached_abort_request:无需额外操作。
  • ngx_http_memcached_finalize_request:无需额外操作。
  • ngx_http_memcached_process_header:模块的业务重点函数。
  • ngx_http_memcached_filter_init:修正从后端服务器收到的内容长度。因为在处理 header 时没有加上这部分长度。
  • ngx_http_memcached_filter:memcached 模块是少有的带有处理正文的回调函数的模块。因为 memcached 模块需要过滤正文末尾 CRLF “END” CRLF,所以实现了自己的 filter 回调函数。处理正文的实际意义是将从后端服务器收到的正文有效内容封装成 ngx_chain_t,并加在 u->out_bufs 末尾。Nginx 并不进行数据拷贝,而是建立 ngx_buf_t 数据结构指向这些数据内存区,然后由 ngx_chain_t 组织这些 buf。

负载均衡模块

模块简介

负载均衡模块用于从upstream指令定义的后端主机列表中选取一台主机。Nginx 先使用负载均衡模块找到一台主机,再使用 upstream 模块实现与这台主机的交互。

配置

在配置文件中,我们如果需要使用 ip hash 的负载均衡算法。我们需要写一个类似下面的配置:

upstream test { ip_hash; server 192.168.0.1; server 192.168.0.2; }

从配置我们可以看出负载均衡模块的使用场景:

  1. 核心指令ip_hash只能在 upstream {}中使用。这条指令用于通知 Nginx 使用 ip hash 负载均衡算法。如果没加这条指令,Nginx 会使用默认的 round robin 负载均衡模块。
  2. upstream {}中的指令可能出现在server指令前,可能出现在server指令后,也可能出现在两条server指令之间。

指令

ip_hash 的指令定义如下:

钩子

负载均衡模块的钩子代码都是有规律的,这里通过 ip_hash 模块来分析这个规律。

这段代码中有两点值得注意。一个是 uscf->flags 的设置,另一个是设置 init_upstream 回调。

设置 uscf->flags

  1. NGX_HTTP_UPSTREAM_CREATE:创建标志,如果含有创建标志的话,Nginx 会检查重复创建,以及必要参数是否填写;
  2. NGX_HTTP_UPSTREAM_MAX_FAILS:可以在 server 中使用 max_fails 属性;
  3. NGX_HTTP_UPSTREAM_FAIL_TIMEOUT:可以在 server 中使用 fail_timeout 属性;
  4. NGX_HTTP_UPSTREAM_DOWN:可以在 server 中使用 down 属性;
  5. NGX_HTTP_UPSTREAM_WEIGHT:可以在 server 中使用 weight 属性;
  6. NGX_HTTP_UPSTREAM_BACKUP:可以在 server 中使用 backup 属性。

在负载均衡模块的指令处理函数中可以设置并修改 upstream{} 中server指令支持的属性。

设置 init_upstream 回调

Nginx 初始化 upstream 时,会在 ngx_http_upstream_init_main_conf 函数中调用设置的回调函数初始化负载均衡模块。

初始化配置

IP hash 模块初始化配置的代码如下:

ngx_http_upstream_init_round_robin(cf, us); us->peer.init = ngx_http_upstream_init_ip_hash_peer;

IP hash 模块首先调用另一个负载均衡模块 Round Robin 的初始化函数,然后再设置自己的处理请求阶段初始化钩子。实际上几个负载均衡模块可以组成一条链表,每次都是从链首的模块开始进行处理。如果模块决定不处理,可以将处理权交给链表中的下一个模块。这里,IP hash 模块指定 Round Robin 模块作为自己的后继负载均衡模块,所以在自己的初始化配置函数中也对 Round Robin 模块进行初始化。

初始化请求

Nginx 收到一个请求后,如果发现需要访问 upstream,就会执行对应的 peer.init 函数。这是在初始化配置时设置的回调函数。这个函数最重要的作用是构造一张表,当前请求可以使用的 upstream 服务器被依次添加到这张表中。

为了讨论 peer.init 的核心,还要看 IP hash 模块的实现:

第一行是设置数据指针,这个指针就是指向前面提到的那张表;

第二行是调用 Round Robin 模块的回调函数对该模块进行请求初始化。

第三行是设置一个新的回调函数get。该函数负责从表中取出某个服务器。除了 get 回调函数,还有另一个r->upstream->peer.free的回调函数。该函数在 upstream 请求完成后调用,负责做一些善后工作。


本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!

相关文章

立即
投稿

微信公众账号

微信扫一扫加关注

返回
顶部