Nginx Handler中不能直接调用同步Redis客户端,因其单线程事件模型会因系统调用阻塞整个worker;需通过subrequest委托给异步upstream模块(如redis2或lua-resty-redis)实现伪异步通信。

在 Nginx Handler 模块中利用 subrequest 机制实现与 Redis 的全异步通信,本质上是一种“伪异步”设计——Nginx 本身不直接支持 Redis 协议的非阻塞 I/O,但可通过子请求将 Redis 交互委托给一个已封装好异步能力的 upstream 模块(如 ngx_http_redis2_module 或基于 lua-resty-redis 的 Lua 实现),从而在 Handler 中保持事件循环不被阻塞。
为什么不能在 Handler 中直接调用 Redis 同步客户端
Nginx 是单线程事件驱动模型(每个 worker 进程一个事件循环)。若在 Handler 中调用传统同步 Redis 客户端(如 libc 库直连),会引发系统调用阻塞(connect、read、write),导致整个 worker 暂停响应其他请求,彻底破坏高并发能力。Nginx 明确禁止在非协程/非 event-driven 上下文中做任何阻塞操作。
subrequest 是如何实现“异步转交”的
subrequest 并非真正的线程或协程,而是 Nginx 内部的轻量级请求调度机制:它把逻辑拆分为父子请求,父请求挂起等待,子请求进入标准 upstream 流程(含连接池、超时控制、事件注册),由 epoll/kqueue 驱动完成 I/O,完成后自动回调父请求的 subrequest done handler。关键点:
- 子请求必须指向一个已支持异步 upstream 的 location(例如
location /redis { redis2_pass ...; }) - 父 Handler 需调用
ngx_http_subrequest注册子请求,并提供回调函数处理结果 - 父请求需返回
NGX_AGAIN让 Nginx 暂停执行,待子请求完成再通过回调恢复 - 所有数据交换通过
ngx_http_request_t->parent和ctx传递,不可依赖栈变量
典型 Handler + subrequest + Redis 的编码结构
以自定义模块中的 Handler 函数为例(C 语言):
static ngx_int_t my_handler(ngx_http_request_t *r) {
ngx_int_t rc;
ngx_http_request_t *sr;
ngx_http_post_subrequest_t *psr;
<pre class="brush:php;toolbar:false;">// 1. 分配子请求上下文(需持久化到 r->pool)
my_ctx_t *ctx = ngx_pcalloc(r->pool, sizeof(my_ctx_t));
if (ctx == NULL) return NGX_HTTP_INTERNAL_SERVER_ERROR;
ngx_http_set_ctx(r, ctx, my_module);
// 2. 发起 subrequest 到预定义的 redis location
rc = ngx_http_subrequest(r, &ngx_str("/redis/get"), NULL, &sr,
my_subrequest_done, NGX_HTTP_SUBREQUEST_IN_MEMORY);
if (rc != NGX_OK) return rc;
// 3. 父请求立即返回 NGX_AGAIN,交还控制权
return NGX_AGAIN;
}
// 子请求完成后的回调 static void my_subrequest_done(ngx_http_request_t r, void data, ngx_int_t rc) { my_ctx_t ctx = ngx_http_get_module_ctx(r->parent, my_module); ngx_buf_t b; ngx_chain_t *cl;
if (rc == NGX_OK && r->upstream && r->upstream->buffer.pos) {
// 解析 Redis 响应(如 RESP 协议格式)
parse_redis_response(r->upstream->buffer.pos, r->upstream->buffer.last, ctx);
}
// 将结果写入父响应体
b = ngx_create_temp_buf(r->parent->pool, 128);
ngx_snprintf(b->pos, 128, "Redis result: %V", &ctx->result);
b->last = ngx_snprintf(b->pos, 128, "Redis result: %V", &ctx->result);
cl = ngx_alloc_chain_link(r->parent->pool);
cl->buf = b;
cl->next = NULL;
r->parent->headers_out.status = NGX_HTTP_OK;
r->parent->headers_out.content_length_n = b->last - b->pos;
ngx_http_send_header(r->parent);
ngx_http_output_filter(r->parent, cl);
}
更推荐的实践路径:用 Lua + lua-resty-redis 替代纯 C Handler
手动编写 C 级 subrequest 回调逻辑复杂、易出内存错误、调试困难。生产环境更推荐:
- 使用
nginx-lua-module(OpenResty) - 在
access_by_lua_block或content_by_lua_block中调用resty.redis - 其底层自动管理 cosocket 连接池和 epoll 事件,真正零阻塞
- 代码简洁可读,支持 pipeline、自动重试、超时熔断等高级特性
例如:local red = redis:new()<br>red:set_timeout(1000)<br>local ok, err = red:connect("127.0.0.1", 6379)<br>if not ok then ngx.exit(500) end<br>local res, err = red:get("mykey")
这段代码在 OpenResty 中是完全异步非阻塞的,且无需手动管理 subrequest 生命周期。











