听说过服务器自己能召唤“分身”处理任务吗?Nginx的子请求功能就是这样一个神奇的存在。
什么是Nginx子请求?
简单来说,子请求就是Nginx正在处理的请求在内部发起的一种级联请求。它外观上很像HTTP请求,但实现上却和HTTP协议乃至网络通信一点儿关系都没有。
它是Nginx内部的一种抽象调用,目的是方便用户把“主请求”的任务分解为多个较小粒度的“内部请求”,并发或串行地访问多个location接口,共同完成整个“主请求”。
子请求与主请求的关系
在Nginx世界里有两种类型的“请求”:“主请求”(main request)和**“子请求”**(subrequest)。
主请求:由HTTP客户端从Nginx外部发起的请求。子请求:由Nginx正在处理的请求在Nginx内部发起的一种级联请求。任何“子请求”也可以再发起更多的“子子请求”,甚至可以玩递归调用(即自己调用自己)。
当一个请求发起一个“子请求”的时候,按照Nginx的术语,习惯把前者称为后者的“父请求”(parent request)。
为什么需要使用子请求?
子请求的核心价值在于解耦复杂逻辑,将一个大任务拆分成多个小任务,让每个location专门负责一件事,符合Unix哲学。
这样做有几个实际好处:
提高效率:子请求的执行效率极高,因为它们的通信是在同一个虚拟主机内部进行的,只调用了若干个C函数,完全不涉及任何网络或者UNIX套接字(socket)通信。代码复用:可以将常用功能(如用户认证、数据查询)封装成独立的location,供多个主请求使用。并行处理:可以并发发起多个子请求,同时处理多个任务,大大减少总体响应时间。逻辑清晰:将复杂业务逻辑分解为多个简单步骤,配置更易维护。
在模块中使用子请求的完整示例
基于auth_request模块的访问控制
模块基于子请求的结果实现客户端授权。它默认未构建,需要使用
ngx_http_auth_request_module配置参数启用。
--with-http_auth_request_module
工作原理:
当客户端请求到达配置了的location时,Nginx会向指定的URI发起子请求如果子请求返回2xx响应代码,则允许访问如果返回401或403,则拒绝访问并返回相应的错误代码子请求返回的任何其他响应代码都被视为错误
auth_request
配置示例:
location /private/ {
auth_request /auth;
# 其他配置...
}
location = /auth {
proxy_pass http://auth_backend;
proxy_pass_request_body off;
proxy_set_header Content-Length "";
proxy_set_header X-Original-URI $request_uri;
}
在这个配置中:
当用户访问 路径时,会先触发到
/private/ 的子请求认证子请求将原始请求的URI通过
/auth 头部传递给认证后端认证后端返回200、401或403状态码决定是否允许访问根据认证结果,主请求继续处理或返回错误
X-Original-URI
基于addition模块的内容处理
模块是一个在响应之前和之后添加文本的过滤器。它默认未构建,应使用
ngx_http_addition_module配置参数启用。
--with-http_addition_module
配置示例:
location / {
add_before_body /before_action;
add_after_body /after_action;
}
在这个例子中,Nginx会在响应体之前添加子请求的内容,在响应体之后添加
/before_action的子请求的内容。
/after_action
指令详解:
:在响应正文之前添加给定的子请求而返回的文本。
add_before_body uri:在响应正文之后添加给定的子请求而返回的文本。
add_after_body uri:除了”text/html”之外,还允许在具有指定MIME类型的响应中添加文本。
addition_types mime-type ...表示与任何MIME类型匹配。
*
基于slice模块的大文件处理
模块是一个过滤器,它将请求拆分为多个子请求,每个子请求返回响应的特定范围。该过滤器提供了对大型响应更有效的缓存。
ngx_http_slice_module
配置示例:
location / {
slice 1m;
proxy_cache cache;
proxy_cache_key $uri$is_args$args$slice_range;
proxy_set_header Range $slice_range;
proxy_cache_valid 200 206 1h;
proxy_pass https://127.0.0.1:8000;
}
在这个示例中,响应被拆分为1MB可缓存的片段。
指令和变量:
:设置片段的大小。零值禁用将响应拆分为片段。
slice size:当前片段范围,采用HTTP字节范围格式,例如
$slice_range。
bytes=0-1048575
子请求的变量处理:一个需要注意的坑
子请求的变量处理有一个重要的细微差别:大多数情况下,父子请求有各自独立的变量值容器,但有些模块(如)发起的子请求会自动共享其父请求的变量值容器。
ngx_auth_request
变量值独立的情况:
location /main {
set $var main;
echo_location /foo;
echo_location /bar;
echo "main: $var";
}
location /foo {
set $var foo;
echo "foo: $var";
}
location /bar {
set $var bar;
echo "bar: $var";
}
请求的输出是:
/main
foo: foo
bar: bar
main: main
这说明和
/foo这两个”子请求”在处理过程中对变量
/bar各自所做的修改都丝毫没有影响到”主请求”
$var。
/main
变量共享的情况:
location /main {
set $var main;
auth_request /sub;
echo "main: $var";
}
location /sub {
set $var sub;
echo "sub: $var";
}
访问接口的结果是:
/main
main: sub
这说明接口对
/sub变量值的修改影响到了主请求
$var。所以
/main模块发起的”子请求”确实是与其”父请求”共享一套Nginx变量的值容器。
ngx_auth_request
如模块这样父子请求共享一套Nginx变量的行为,虽然可以让父子请求之间的数据双向传递变得极为容易,但是对于足够复杂的配置,却也经常导致不少难于调试的诡异bug。
ngx_auth_request
实际应用案例:构建一个页面组装器
假设我们需要构建一个动态页面,包含用户信息、文章内容和评论列表,这些数据来自不同的后端服务。使用子请求可以这样实现:
location /article/{
auth_request /user/auth;
add_before_body /user/header;
add_after_body /article/comments;
proxy_pass http://article_backend;
}
location /user/auth {
internal;
proxy_pass http://auth_backend/check;
proxy_pass_request_body off;
proxy_set_header Content-Length "";
}
location /user/header {
internal;
proxy_pass http://user_backend/header;
}
location /article/comments {
internal;
proxy_pass http://comment_backend/list;
}
这个配置实现了:
访问控制:先通过子请求验证用户身份个性化内容:在正文前添加用户相关的头部信息主要内容:代理到文章后端获取文章内容动态组装:在正文后添加评论列表
/user/auth
所有这些都是通过子请求并行处理的,大大提高了效率。
子请求的缓存策略
为了提高性能,可以对子请求的结果进行缓存。从Nginx 1.7.3开始,可以缓存对授权子请求的响应(使用proxy_cache、proxy_store等)。
缓存配置示例:
location /auth {
proxy_pass http://auth_backend;
proxy_cache auth_cache;
proxy_cache_key "$cookie_session$request_uri";
proxy_cache_valid 200 10m;
proxy_cache_use_stale updating error timeout;
}
此配置会缓存认证结果10分钟,基于会话cookie和请求URI作为缓存键。
性能考虑和最佳实践
虽然子请求很强大,但使用时需要注意以下几点:
避免循环子请求:子请求可以发起子子请求,但要避免无限递归合理设置超时:对于子请求,设置适当的超时时间,避免一个缓慢的子请求拖垮整个服务谨慎使用变量共享:了解你使用的模块是否共享变量上下文,避免意外的变量覆盖利用缓存:对不经常变化的子请求结果进行缓存,提高性能错误处理:总有考虑子请求失败的情况,提供适当的降级方案
总结
Nginx的子请求是一个极其强大的功能,它允许我们将复杂的Web请求分解为多个内部请求,通过多个location的协作完成复杂任务。
无论是用于用户认证、内容组装还是大文件处理,子请求都能提供优雅高效的解决方案。
不过,在使用子请求时,要特别注意变量的作用域问题,不同模块的行为可能不同。合理使用子请求,可以让你的Nginx配置更加强大和灵活,轻松应对复杂的业务场景。
现在,就去找个项目试试“摇人儿”的神奇魔力吧!
附表:常用子请求模块对比
|
模块名称 |
功能 |
主要指令 |
适用场景 |
|
auth_request |
访问控制 |
auth_request |
API认证、权限检查 |
|
addition |
内容处理 |
add_before_body, add_after_body |
页面组装、统一页眉页脚 |
|
slice |
大文件分片 |
slice |
视频/大文件缓存、断点续传 |
|
echo |
高级子请求 |
echo_location, echo_subrequest_async |
复杂业务逻辑、多数据源聚合 |