nginx的模块化思想

Feb 14, 2010

【如果你想详细了解nginx的模块化思想的话,请点击:http://www.nginx.com.cn/?p=201,本文是我在浏览完上述网址后的总结,备忘只用。】

大体了解

Nginx模块主要有3种角色:

  • handlers(处理模块) 用于处理HTTP请求,然后产生输出
  • filters(过滤模块) 过滤handler产生的输出
  • load-balancers(负载均衡模块)当有多于一台的后端备选服务器时,选择一台转发HTTP请求

模块可以做任何你分配给web服务器的实际工作:当Nginx发送文件或者转发请求到其他服务器,有处理模块为其服务;当需要Nginx把输出压缩或者在服务端加一些东西,可以用过滤模块;Nginx的核心模块主要管理网络层和应用层协议,以及启动针对特定应用的一系列候选模块。集中式的体系结构让你可以随心所欲得实现一些功能强大的内部单元。如果处理模块把请求反向代理到后端的服务器,就变成另外一类的模块:负载均衡模块。负载均衡模块的配置中有一组后端服务器,当一个HTTP请求过来时,它决定哪台服务器应当获得这个请求。Nginx的负载均衡模块采用两种方法:轮转法,它处理请求就像纸牌游戏一样从头到尾分发;IP哈希法,在众多请求的情况下,它确保来自同一个IP的请求会分发到相同的后端服务器。

nginx的整个工作流程如下:客户端发送HTTP请求->Nginx基于配置文件中的位置选择一个合适的处理模块->(如果有)负载均衡模块选择一台后端服务器->处理模块进行处理并把输出缓冲放到第一个过滤模块上->第一个过滤模块处理后输出给第二个过滤模块->然后第二个过滤模块又到第三个->依此类推->最后把回复发给客户端。

有用的一些东西

nginx模块的配置结构体名称约定是:ngx_http__(main|srv|loc)_conf_t

模块的指令出现在静态数组ngx_command_t:

struct ngx_command_t {
ngx_str_t             name;
ngx_uint_t            type;
char               *(*set)(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
ngx_uint_t            conf;
ngx_uint_t            offset;
void                 *post;
};

name是指令的字符串,type是标识的集合,表明这个指令在哪里出现合法的,指令的参数个数。结构体成员set是一个函数指针,用来设定模块的配置,这个设定函数有三参数:指向ngx_conf_t结构体的指针,包含从指令后面传过来的参数;指向当前ngx_command_t结构体的指针;指向自定义模块配置结构体的指针。conf和offset是关于上面的内嵌函数存放的,conf告诉nginx把返回值是放在全局配置部分,主机配置部分还是位置配置部分,offset确定到底是保存在结构体的哪个位置。最后post指向模块在读配置的时候需要的一些零碎变量,一般是NULL。

模块的上下文:静态的ngx_http_module_t结构体,包含大量的函数引用。用来创建三个部分的配置和合并配置。一般命名为:ngx_http__module_ctx。该结构体包含很多函数指针,而大多数处理模块只使用最后两个:ngx_http__create_loc_conf(为特定位置部分的配置结构体分配内存);ngx_http__merge_loc_conf(合并函数,检测读入数值是否有效)。

模块的定义:结构体ngx_module_t,一般命名为ngx_http__module,包含模块的主要内容和执行部分,他包含模块上下文结构体ngx_http__module_ctx和指令结构体ngx_http__commands.

模块的注册

处理模块的注册:通过指令结构体中的应用函数(第三项)来注册,该函数会获得这个位置配置的核心结构体,然后为它分配一个处理函数。

过滤模块的注册:通过ngx_http__module_ctx中函数注册.

剖析处理模块:nginx通过调用/src/http/ngx_http_config.h中的宏ngx_http_get_module_loc_conf来获得位置配置结构体。在结构体ngx_http_header_out_t存放回复头部,同时该结构体包含在请求结构体ngx_http_request_t中。在回复产生后,nginx为回复分配一个buffer,同时把这个buffer连接到一个链表中,由create_request回调函数完成,而process_header函数把回复指针移到客户端可以接收到的部分。

负载均衡模块

负载均衡模块决定哪个后端服务器可以分到特定的请求;现在的实现有通过轮转法或者对于请求的某些部分进行哈希处理。一个负载均衡模块主要包含一下六个方面:

  1. 激活配置指令需要调用一个注册函数
  2. 注册函数定义合法的服务器选项,同时注册上游主机初始化函数
  3. 上游主机初始化函数在配置确认好了以后被调用:
    • 解析服务器名称,指向特定的IP地址
    • 为每个套接字分配空间
    • 设定同伴初始函数(peer initialization function)的回调入口
  4. 同伴初始化函数在每个请求到临的时候调用一次,设置一个负载均衡函数可以进入和操作的数据结构体。
  5. 负载均衡函数(load-balancing function)决定这个请求的去向;在客户端请求来时至少调用一次。
  6. 在和特定的后端服务器结束通信的时候,同伴释放函数(peer release function)会更新统计(通信是否成功)

激活指令

指令声明,既确定了他们的有效存在位置也给出会调用哪个函数。对于负载均衡的模块应该有的标识NGX_HTTP_UPS_CONF,以便Nginx知道这个指令只在upstream配置部分有效。它应该提供一个指向注册函数的指针。下面是upstream_hash模块的指令声明:

{ ngx_string("hash"),
NGX_HTTP_UPS_CONF|NGX_CONF_NOARGS,
ngx_http_upstream_hash,
0,
0,
NULL },

其他没什么了。

上游主机初始化函数

解析主机名称,为每个socket分配空间,并分配(another)回调函数

同伴初始化函数

每当客户端请求的时候,同伴初始化函数就被调用一次。它设定一个结构体,模块会用它选择一个合适的服务器来服务这个请求;该结构体在后端服务器重试的时候是一致的。所以很容易在这里追踪服务器的连接失败或计算过的哈希值。按照约定,这个结构体称为ngx_http_upstream__peer_data_t。

此外,同伴初始化函数设定两个回调函数:

  • get: 负载均衡函数
  • free:同伴释放函数(通常在连接结束的时候更新一些统计信息)

看起来还不够,它还初始化了一个名叫tries的变量。只要tries是正的,Nginx就会重试这个负载均衡函数。当tries是零的时候,Nginx会放弃。这取决于get和free函数的tries设定的初始值。

负载均衡函数

在这里模块选择一台上游服务器。负载均衡函数的原型是这样的:

static ngx_int_t
ngx_http_upstream_get__peer(ngx_peer_connection_t *pc, void *data);

Data是我们的结构体,包含客户端的连接等有效信息。Pc包含我们要连接的服务器信息。负载均衡函数的主要工作就是往pc->sockaddr, pc->socklen, 和pc->name里面填进数据。如果你熟悉网络编程,那么对这些变量应该会很熟悉;但它们在这里不是很重要。我们不必留意他们代表什么;只需要知道去哪里找到合适的值填进去。
这个函数需要找到一列服务器,选择一个,把值赋给pc。让我们看看upstream_hash模块是怎么做的。
此前在函数调用ngx_http_upstream_init_hash ,upstream_hash 模块已经把服务器列表存在结构体ngx_http_upstream_hash_peer_data_t中。这个结构体现在是有效的:
ngx_http_upstream_hash_peer_data_t *uhpd = data;
现在同伴列表存在uhpd->peers->peer,根据分配好的哈希值和服务器数目,从这个数组中选择一台同伴服务器。
ngx_peer_addr_t *peer = &uhpd->peers->peer[uhpd->hash % uhpd->peers->number];
终于大功告成:

pc->sockaddr = peers->sockaddr;
pc->socklen  = peers->socklen;
pc->name     = &peers->name;
return NGX_OK;

如果负载均衡函数返回NGX_OK,它表明,继续尝试这台服务器。如果返回NGX_BUSY,意味着所有后台主机均无效,Nginx应该再尝试一次。

Posted by admin nginx , 负载均衡 , 模块化

Comments

  1. Avatar

Get In Touch With Us ...