如何用Nginx和lua-resty-upstream-healthcheck实现长尾词的主动节点健康检测?
- 内容介绍
- 文章标签
- 相关推荐
本文共计1260个文字,预计阅读时间需要6分钟。
由于 `lua-resty-upstream-healthcheck` 不是 Nginx 官方模块,也不在 `upstream` 模块中通过 `health_check` 指令启用,这是 Nginx Plus 的功能。您已安装了 Lua 库,但没有调用它,因此 Nginx 完全不知道您正在尝试进行什么测试。
真正生效的方式是:手动创建一个定时器(ngx.timer.at),在回调里用 checker:check() 主动轮询后端节点,并根据结果动态更新 shared_dict 中的节点状态。Nginx 的 upstream 本身不会自动读这个状态,你还得配合 balancer_by_lua* 阶段做路由决策。
- 必须在
init_worker_by_lua_block里启动健康检查器,否则 worker 进程间状态不共享或重复初始化 -
shared_dict要提前在http块声明,大小建议 ≥1m(存 IP+端口+状态+时间戳) - 检查器默认只标记“异常”,不自动剔除节点;剔除逻辑必须你自己在
balancer_by_lua_block里查字典、过滤掉 down 的节点
如何在 balancer_by_lua_block 里跳过已下线节点
这是最关键的路由控制点。Nginx 在这里把请求交给哪个后端,完全由你决定。不能依赖 upstream 的内置负载逻辑,得自己从 upstream 配置中提取所有 server,再逐个查健康状态。
示例逻辑片段(放在 location 或 upstream 对应的 balancer_by_lua_block 内):
local peers = require "resty.upstream.healthcheck".get_peers("my_backend") local up_peers = {} for _, p in ipairs(peers) do local state = checker_dict:get("health:" .. p.host .. ":" .. p.port) if state == "up" then table.insert(up_peers, p) end end if #up_peers == 0 then ngx.exit(503) end local idx = ngx.ctx.balancer_index or 0 idx = idx % #up_peers + 1 ngx.ctx.balancer_index = idx local peer = up_peers[idx] balancer.set_current_peer(peer.host, peer.port)
-
checker_dict是你定义的shared_dict名,用来存健康状态,key 格式要和检查器写入的一致 - 务必加空列表兜底(
#up_peers == 0),否则所有请求会卡死或 fallback 到默认行为 - 如果用了 keepalive,注意
set_current_peer后连接池是否匹配;不匹配可能触发新建连接,影响性能
probe 接口返回 200 但节点仍被标为 down
常见原因是 probe 请求没走通,或者响应内容不符合检查器预期。默认配置下,lua-resty-upstream-healthcheck 不只看 HTTP 状态码,还会校验响应体是否包含指定字符串(rise_count/fall_count 是连续成功/失败次数,不是单次判定)。
- 检查
http块中checker:configure的port和uri:比如后端服务监听的是8080,但 probe 发到了80,必然超时 - 确认后端 probe 接口是否真返回了
body_pattern指定的内容(默认是空字符串,但若你设了"ok",而接口返回{"status":"ok"},就不匹配) - 用
tcpdump或strace -p $(pidof nginx)抓包验证 probe 是否发出、目标是否可达;有时候防火墙或 SELinux 会拦截 worker 进程的 outbound 连接 - 日志级别调到
debug,并在error_log中搜索healthcheck,能看到每次 probe 的耗时、状态码、body 截断内容
多个 worker 下状态不同步或反复震荡
根本原因是每个 worker 独立运行 timer,又各自维护一份 checker 实例,但 shared_dict 是共享的——所以问题不在存储,而在“谁来写”和“写什么”。如果多个 worker 同时 probe 同一节点,且判断节奏不一致,就容易出现 A 判 up、B 判 down 的情况。
- 必须用
init_worker_by_lua_block启动 checker,并确保只有一个 worker(比如用math.random() < 0.1加锁)执行 probe,其他 worker 只读状态 - 不要在
balancer_by_lua_block里调用checker:check(),那会导致每请求都 probe,彻底压垮后端 - 调整
interval(建议 ≥5s)和timeout(建议 ≤1s),避免 probe 积压;太短的间隔在高并发下极易误判 - 如果后端本身有抖动(如 GC 导致某次响应慢于 300ms),考虑把
fall_count设为 3~5,而不是默认的 2
主动探测这事,核心从来不是“能不能发请求”,而是“怎么让所有 worker 对同一节点的状态认知一致”,以及“怎么不让探测本身成为新的故障源”。细节都在 timer 控制、shared_dict 键设计、和 balancer 阶段的过滤逻辑里。
本文共计1260个文字,预计阅读时间需要6分钟。
由于 `lua-resty-upstream-healthcheck` 不是 Nginx 官方模块,也不在 `upstream` 模块中通过 `health_check` 指令启用,这是 Nginx Plus 的功能。您已安装了 Lua 库,但没有调用它,因此 Nginx 完全不知道您正在尝试进行什么测试。
真正生效的方式是:手动创建一个定时器(ngx.timer.at),在回调里用 checker:check() 主动轮询后端节点,并根据结果动态更新 shared_dict 中的节点状态。Nginx 的 upstream 本身不会自动读这个状态,你还得配合 balancer_by_lua* 阶段做路由决策。
- 必须在
init_worker_by_lua_block里启动健康检查器,否则 worker 进程间状态不共享或重复初始化 -
shared_dict要提前在http块声明,大小建议 ≥1m(存 IP+端口+状态+时间戳) - 检查器默认只标记“异常”,不自动剔除节点;剔除逻辑必须你自己在
balancer_by_lua_block里查字典、过滤掉 down 的节点
如何在 balancer_by_lua_block 里跳过已下线节点
这是最关键的路由控制点。Nginx 在这里把请求交给哪个后端,完全由你决定。不能依赖 upstream 的内置负载逻辑,得自己从 upstream 配置中提取所有 server,再逐个查健康状态。
示例逻辑片段(放在 location 或 upstream 对应的 balancer_by_lua_block 内):
local peers = require "resty.upstream.healthcheck".get_peers("my_backend") local up_peers = {} for _, p in ipairs(peers) do local state = checker_dict:get("health:" .. p.host .. ":" .. p.port) if state == "up" then table.insert(up_peers, p) end end if #up_peers == 0 then ngx.exit(503) end local idx = ngx.ctx.balancer_index or 0 idx = idx % #up_peers + 1 ngx.ctx.balancer_index = idx local peer = up_peers[idx] balancer.set_current_peer(peer.host, peer.port)
-
checker_dict是你定义的shared_dict名,用来存健康状态,key 格式要和检查器写入的一致 - 务必加空列表兜底(
#up_peers == 0),否则所有请求会卡死或 fallback 到默认行为 - 如果用了 keepalive,注意
set_current_peer后连接池是否匹配;不匹配可能触发新建连接,影响性能
probe 接口返回 200 但节点仍被标为 down
常见原因是 probe 请求没走通,或者响应内容不符合检查器预期。默认配置下,lua-resty-upstream-healthcheck 不只看 HTTP 状态码,还会校验响应体是否包含指定字符串(rise_count/fall_count 是连续成功/失败次数,不是单次判定)。
- 检查
http块中checker:configure的port和uri:比如后端服务监听的是8080,但 probe 发到了80,必然超时 - 确认后端 probe 接口是否真返回了
body_pattern指定的内容(默认是空字符串,但若你设了"ok",而接口返回{"status":"ok"},就不匹配) - 用
tcpdump或strace -p $(pidof nginx)抓包验证 probe 是否发出、目标是否可达;有时候防火墙或 SELinux 会拦截 worker 进程的 outbound 连接 - 日志级别调到
debug,并在error_log中搜索healthcheck,能看到每次 probe 的耗时、状态码、body 截断内容
多个 worker 下状态不同步或反复震荡
根本原因是每个 worker 独立运行 timer,又各自维护一份 checker 实例,但 shared_dict 是共享的——所以问题不在存储,而在“谁来写”和“写什么”。如果多个 worker 同时 probe 同一节点,且判断节奏不一致,就容易出现 A 判 up、B 判 down 的情况。
- 必须用
init_worker_by_lua_block启动 checker,并确保只有一个 worker(比如用math.random() < 0.1加锁)执行 probe,其他 worker 只读状态 - 不要在
balancer_by_lua_block里调用checker:check(),那会导致每请求都 probe,彻底压垮后端 - 调整
interval(建议 ≥5s)和timeout(建议 ≤1s),避免 probe 积压;太短的间隔在高并发下极易误判 - 如果后端本身有抖动(如 GC 导致某次响应慢于 300ms),考虑把
fall_count设为 3~5,而不是默认的 2
主动探测这事,核心从来不是“能不能发请求”,而是“怎么让所有 worker 对同一节点的状态认知一致”,以及“怎么不让探测本身成为新的故障源”。细节都在 timer 控制、shared_dict 键设计、和 balancer 阶段的过滤逻辑里。

