Nginx 通过内置 resolver 机制实现 DNS 异步解析,不依赖阻塞的 getaddrinfo(),而是基于 epoll/kqueue 事件驱动模型自建轻量级 DNS 客户端,将查询嵌入 worker 进程事件循环,回调处理响应并缓存结果,确保高并发下不阻塞其他请求。

Nginx 本身没有传统意义上的“Event Loop”概念(如 Node.js 那样显式暴露的循环对象),但它基于 异步非阻塞事件驱动模型,通过底层 I/O 多路复用(epoll/kqueue)+ 自定义 resolver 机制,实现了 DNS 解析的异步化。关键不是“实现一个 Event Loop”,而是 把 DNS 查询嵌入到 Nginx 的事件调度框架中,不阻塞 worker 进程。
Nginx 的 DNS 异步解析靠的是内置 resolver 机制
Nginx 的 resolver 指令启用的不是系统调用 getaddrinfo()(那是同步、阻塞的),而是自己实现的一套 轻量级 DNS 客户端逻辑:
- 启动时初始化一个全局
ngx_resolver_t结构体,管理 DNS 服务器列表、缓存(红黑树)、待处理查询队列; - 每次需要解析域名(比如
proxy_pass http://$host中的变量),就构造 DNS 查询报文,用sendto()发送到配置的 nameserver; - 不等待响应,而是把本次查询注册进事件循环:当 socket 可读时,由 Nginx 的 event handler 触发回调函数
ngx_resolver_resend_handler或ngx_resolver_process_response; - 解析结果写入缓存(按域名 + TTL 管理有效期),后续请求可直接命中。
这个过程完全运行在 worker 进程的事件循环内,不创建新线程,不调用阻塞系统调用,自然就是“异步非阻塞”的。
OpenResty 的 lua-resty-dns 进一步强化异步能力
OpenResty 在 Nginx 基础上,通过 Lua API 把 resolver 能力暴露给脚本层,但它的异步性依然依赖 Nginx 底层:
-
resty.dns.resolver:new()创建的解析器,内部仍复用 Nginx 的ngx_resolver_t或封装其 UDP socket; -
r:query()调用后立即返回nil(不是等待结果),实际是发起一次异步查询,并注册回调; - 查询完成时,Lua 代码在
balancer_by_lua_block或content_by_lua_block中被再次调度执行,此时才能拿到answers; - 整个流程不 yield 主线程(不需要
coroutine.yield),也不阻塞其他请求 —— 因为底层仍是事件驱动 + callback。
✅ 关键点:它没自己实现 Event Loop,而是 深度集成 Nginx 的事件调度器。Lua 代码只是事件循环中的一个可执行片段。
为什么不能直接用系统 getaddrinfo?
因为 getaddrinfo() 是 POSIX 同步接口,调用时会阻塞当前 worker 进程,哪怕只等几十毫秒,也会导致该 worker 无法处理其他连接 —— 违背 Nginx 高并发设计初衷。所以 Nginx 必须绕过 libc,自己发 DNS 包、收响应、管理超时和重试。
实际效果:一次解析不影响其他请求
假设你配置了:
location /api {
resolver 8.8.8.8 valid=5s;
set $backend "svc.example.com";
proxy_pass http://$backend;
}- 第一个请求进来,Nginx 发 DNS 查询 → 继续处理其他请求;
- DNS 响应到达 → 触发回调 → 缓存结果 → 下一个匹配的请求直接取缓存;
- 即使 DNS 服务器延迟高或超时,也只影响本次查询的那条请求路径,其余 worker 任务照常运转。
这就是“异步”的真实含义:解耦 I/O 等待与业务执行,让 CPU 时间片始终被有效利用。
不复杂但容易忽略


















