1.引言:
1.1反向代理:
反向代理是充当Web服务器网关的代理服务器。当您将请求发送到使用反向代理的Web服务器时,他们将先转到反向代理,由该代理将确定是将其路由到Web服务器还是将其阻止。
这意味着有了反向代理,您永远不会与使用它的Web服务器进行直接通信。可以将它们看作web服务器或服务器集群的某种包装器。通过负载平衡和缓存,它们可以保护web免遭攻击,并提供更好的web性能
1.2ngxin访问第三方服务:
Nginx提供了两种全异步方式来与第三方服务器通信:upstream与subrequest。
1.2.1upstream
可以保证在与第三方服务器交互时(包括三次握手建立TCP连接、 发送请求、 接收响应、 四次握手关闭TCP连接等) 不会阻塞Nginx进程处理其他请求, 也就是说, Nginx仍然可以保持它的高性能
1.2.2:subrequest
只是分解复杂请求的一种设计模式, 它本质上与访问第三方服务没有任何关系,但从HTTP模块开发者的角度而言, 使用subrequest访问第三方服务却很常用, 当然,subrequest访问第三方服务最终也是基于upstream实现的
1.2.3:两者的区别
从名称中可以看出, upstream被定义为访问上游服务器, 也就是说, 它把Nginx定义为代理服务器, 首要功能是透传, 其次才是以TCP获取第三方服务器的内容。 Nginx的HTTP反向代理模块就是基于upstream方式实现的
顾名思义, subrequest是从属请求的意思, 在这里我们更倾向于称它为子请求, 也就是说,subrequest将会为客户请求创建子请求, 这是为什么呢? 因为异步无阻塞程序的开发过于复
杂, 所以HTTP框架提供了这种机制将一个复杂的请求分解为多个子请求, 每个子请求负责
一种功能, 而最初的原始请求负责构成并发送响应给客户端.
综合举例子:
用subrequest访问第三方服务, 一般都是派生出子请求访问上游服务器, 父请求在完全取得上游服务器的响应后再决定如何处理来自客户端的请求。 这样做的好处是每个子请求专注于一种功能。 例如, 对于一个子请求, 通常在NGX_HTTP_CONTENT_PHASE阶段仅会使用一个HTTP模块处理, 这大大降低了模块开发的复杂度。 从HTTP框架的内部来说, subrequest与upstream也完全不同,upstream是从属于用户请求的, subrequest与原始的用户请求相比是一个(或多个) 独立的新请求, 只是新的子请求与原始请求之间可以并发的处理。
2.upstream的使用方法:
2.1ngxin的核心功能-反向代理举例
反向代理模块是在先接收完客户请求的HTTP包体后, 才向上游服务器建立连接并转
发请求的。 假设用户要上传大小为1GB的文件, 由于网速限制, 文件完整地到达Nginx需要
10小时, 恰巧Nginx与上游服务器间的网络也很差(当然这种情况很少见) , 反向代理这个
请求到上游服务也需要10小时, 因此, 根据用户的网速也许本来只要10个小时的上传过程,
最终可能需要20个小时才能完成
2.2 upstream的工作机制
1) 首先需要创建上面介绍的upstream成员, 注意,upstream在初始状态下是NULL空指
针。 可以调用HTTP框架提供好的ngx_http_upstream_create方法来创建upstream。
upstream 存在于一个ngx_http_request_t r中:
typedef struct ngx_http_request_s ngx_http_request_t;
struct ngx_http_request_s {
…
ngx_http_upstream_t *upstream;
…
};
2) 接着设置上游服务器的地址。在HTTP反向代理功能中似乎只能使用在nginx.conf中配置好的上游服务器 ,而实际上upstream机制并没有这种要求,用户能够以任意方式指定上游服务器的IP地址。 例如, 可以从请求的URL或HTTP头部中动态地获取上游服务器地址, ngx_http_upstream_t中的resolved成员就可以帮助用户设置上游服务器
3) 由于upstream非常灵活, 在各个执行阶段中都会试图回调使用它的HTTP模块实现的8
个方法 , 用户要定义好这些回调方法。
4) 在http模块中, 调用ngx_http_upstream_init方法即可启动upstream机制。 注意,自己的模板回调方法此时必须返回NGX_DONE, 这是在要求HTTP框架不要按阶段继续向下处理请求了, 同时它告诉HTTP框架请求必须停留在当前阶段, 等待某个HTTP模块主动地继续处理这个请求(例如, 在上游服务器主动关闭连接时, upstream模块就会主动地继续处理这个请求, 很可能会向客户端发送502响应码)。
2.3upstream的执行机制
下图所示的upstream流程包含了epoll模块多次调度、 处理一个请求的过程, 它虽然与实
际代码执行关系不大, 但却指出了最常用的3个回调方法——create_request、
process_header、 finalize_request是如何回调的。
2.4 结ngx_http_upstream_t 结构体
typedef struct ngx_http_upstream_s ngx_http_upstream_t;
struct ngx_http_upstream_s {
…
ngx_chain_t *request_bufs;
ngx_http_upstream_conf_t *conf;
ngx_http_upstream_resolved_t *resolved;
ngx_buf_t buffer;
// 构造发往上游服务器的请求内容
ngx_int_t (*create_request)(ngx_http_request_t *r);
ngx_int_t (*process_header)(ngx_http_request_t *r);
// 销毁upstream请求时调用
void (*finalize_request)(ngx_http_request_t *r, ngx_int_t rc);
// 5个可选的回调方法
ngx_int_t (*input_filter_init)(void *data);
ngx_int_t (*input_filter)(void *data, ssize_t bytes);
....
//SSL协议访问上游服务器
unsigned ssl:1;
unsigned buffering:1; …
};
结构成员简介:
1.ngx_chain_t *request_bufs;
request_bufs决定发送什么样的请求给上游服务器, 在实现create_request方法时需要设置它
2.conf and resolve
一个限制参数 另外一个是解析参数
3.ngx_buf_t buffer;
buffer成员存储接收自上游服务器发来的响应内容, 由于它会被复用, 所以具有下列多种意义:
a)在使用process_header方法解析上游响应的包头时,buffer中将会保存完整的响应包头;
b)当下面的buffering成员为1, 而且此时upstream是向下游转发上游的包体时,buffer没有意义;
c)当buffering标志位为0时,buffer缓冲区会被用于反复地接收上游的包体, 进而向下游转发;
d)当upstream并不用于转发上游包体时,buffer会被用于反复接收上游的包体,HTTP模块实现的input_filter方法需要关注它
4.ngx_int_t (*process_header)(ngx_http_request_t *r);
收到上游服务器的响应后就会回调process_header方法。 如果process_header返NGX_AGAIN, 那么是在告诉upstream还没有收到完整的响应包头, 此时, 对于本次upstream请求来说, 再接收到上游服务器发来的TCP流时, 还会调用process_header方法处理, 直到process_header函数返回非NGX_AGAIN值这一阶段才会停止
5.unsigned buffering:1;
在向客户端转发上游服务器的包体时才有用。 当buffering为1时, 表示使用多个缓冲区以及磁盘文件来转发上游的响应包体。当Nginx与上游间的网速远大于Nginx与下游客户端间的网速时, 让Nginx开辟更多的内存甚至使用磁盘文件来缓存上游的响应包体, 这是有意义的, 它可以减轻上游服务器的并发压力。
当buffering为0时, 表示只使用上面的这一个
buffer缓冲区来向下游转发响应包体
总的解析:
upstream有3种处理上游响应包体的方式, 但HTTP模块如何告诉upstream使用哪一种方式处理上游的响应包体呢?
当请求的ngx_http_request_t结构体中subrequest_in_memory标志位为1时, 将采用第1种方式, 即upstream不转发响应包体到下游,由HTTP模块实现的input_filter方法处理包体;
当subrequest_in_memory为0时, upstream会转发响应包体。
当ngx_http_upstream_conf_t配置结构体中的buffering标志位为1时, 将开启更多的内存和磁盘文件用于缓存上游的响应包体, 这意味上游网速更快;
当buffering为0时, 将使用固定大小的缓冲区(就是上面介绍的buffer缓冲区) 来转发响应包体
2.5解析conf
ngx_http_upstream_t中的conf成员, 它用于设置upstream模块处理请求时的参数, 包括连接、 发送、 接收的超时时间等。
typedef struct {
…
//连接上游服务器的超时时间, 单位为毫秒
ngx_msec_t connect_timeout;
// 发送TCP包到上游服务器的超时时间, 单位为毫秒
ngx_msec_t send_timeout;
// 接收TCP包到上游服务器的超时时间, 单位为毫秒
ngx_msec_t read_timeout;
…
} ngx_http_upstream_conf_t;
事实上,HTTP反向代理模块在nginx.conf文件中提供的配置项大都是用来设置ngx_http_upstream_conf_t结构体中的成员的。 上面列出的3个超时时间是必须要设置的, 因为它们默认为0, 如果不设置将永远无法与上游服务器建立起TCP连接(因为connect_timeout值为0) 。
接下来以设置connect_timeout连接超时时间为例说明如何编写ngx_command_t来读取配置
文件
static ngx_command_t ngx_http_mytest_commands[] =
{…
{ ngx_string("upstream_connect_timeout"),
NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
ngx_conf_set_msec_slot,
NGX_HTTP_LOC_CONF_OFFSET,
/*给出
connect_timeout成员在
ngx_http_mytest_conf_t结构体中的偏移字节数
*/
offsetof(ngx_http_mytest_conf_t, upstream.connect_timeout), NULL },
…
}
效果:nginx.conf文件中的upstream_conn_timeout配置项将被解析到ngx_http_mytest_conf_t
结构体的upstream.connect_timeout成员中。 在处理实际请求时, 只要把ngx_http_mytest_conf_t
配置项的upstream成员赋给ngx_http_upstream_t中的conf成员即
ngx_http_mytest_conf_t *mycf =
(ngx_http_mytest_conf_t *) ngx_http_get_module_loc_conf(r, ngx_http_mytest_module_t)
2.6设置需要访问的第三方服务器
ngx_http_upstream_t结构中的resolved成员可以直接设置上游服务器的地址。
首先介绍一下resolved的类型。
typedef struct {
…
//
地址个数
ngx_uint_t naddrs;
// 上游服务器的地址
struct sockaddr *sockaddr;
socklen_t socklen;
…
} ngx_http_upstream_resolved_t;