fix(proxy-node): 心跳和健康检查增加 tunnel 节点状态修正能力

- 心跳处理:收到心跳说明 tunnel 连通,若状态非 ONLINE 则修正
- 健康检查:移除 OFFLINE 过滤,允许检测 tunnel 重连后的状态恢复
- 前端配额刷新:就地更新 key metadata,避免重拉列表导致分页重置
This commit is contained in:
fawney19
2026-02-26 17:51:49 +08:00
parent 3fff147b9b
commit f2f2a2dbc4
3 changed files with 25 additions and 9 deletions

View File

@@ -2,7 +2,7 @@
ProxyNode 心跳检测调度器
定期检查 proxy_nodes 的 tunnel 连接状态,更新节点状态:
- tunnel_connected=True -> ONLINE
- tunnel 实际连接中 -> ONLINE包括从 OFFLINE 恢复的情况)
- tunnel 刚断开 (<60s) -> UNHEALTHY缓冲期避免正在进行的请求被立即切走
- tunnel 断开超过 60s -> OFFLINE
"""
@@ -59,12 +59,12 @@ class ProxyNodeHealthScheduler:
db = create_session()
try:
now = datetime.now(timezone.utc)
# 检查非手动节点(手动节点无心跳,始终保持 ONLINE
# 非手动节点均为 tunnel 模式,由 tunnel 连接状态决定
# 检查所有非手动节点(手动节点无心跳,始终保持 ONLINE
# 包括 OFFLINE 节点tunnel 重连后如果 _update_tunnel_status 失败,
# 健康检查需要能根据 TunnelManager 内存状态将其恢复为 ONLINE
nodes = (
db.query(ProxyNode)
.filter(
ProxyNode.status != ProxyNodeStatus.OFFLINE,
ProxyNode.is_manual == False, # noqa: E712
)
.all()

View File

@@ -312,7 +312,7 @@ class ProxyNodeService:
total_requests: int | None = None,
avg_latency_ms: float | None = None,
) -> ProxyNode:
"""处理节点心跳(仅 tunnel 模式节点,心跳更新指标但不改变状态"""
"""处理节点心跳(仅 tunnel 模式节点,更新指标并修正状态不一致"""
node = db.query(ProxyNode).filter(ProxyNode.id == node_id).first()
if not node:
raise NotFoundException(f"ProxyNode {node_id} 不存在", "proxy_node")
@@ -323,7 +323,12 @@ class ProxyNodeService:
)
now = datetime.now(timezone.utc)
# 状态由 tunnel WebSocket 连接建立/断开时决定,心跳仅更新指标
# 心跳通过 tunnel 连接传输,能收到心跳说明 tunnel 一定连通。
# 如果状态不是 ONLINE例如 _update_tunnel_status 执行失败),修正状态。
if node.status != ProxyNodeStatus.ONLINE:
node.status = ProxyNodeStatus.ONLINE
node.tunnel_connected = True
node.updated_at = now
node.last_heartbeat_at = now
if heartbeat_interval is not None:
node.heartbeat_interval = heartbeat_interval