Skip to content

fix: 修复server模式心跳检测的两个关键缺陷 - #242

Open
animacaeli wants to merge 1 commit into
dotnetcore:masterfrom
animacaeli:fix/server-heartbeat-lastheartbeat-update
Open

fix: 修复server模式心跳检测的两个关键缺陷#242
animacaeli wants to merge 1 commit into
dotnetcore:masterfrom
animacaeli:fix/server-heartbeat-lastheartbeat-update

Conversation

@animacaeli

Copy link
Copy Markdown

问题描述

一个典型场景可以复现该问题:

  1. 客户端通过注册中心接入,指定心跳模式为 server,注册时提供的 IP+Port 在云服务器上端口未开放
  2. 管理后台显示该服务状态为异常,最后响应时间停留在注册时间
  3. 运维将云服务器对应端口开放后,发现:
    • 服务状态仍然是异常,没有恢复为健康
    • 最后响应时间没有更新,仍显示注册时的时间
    • 看上去服务注册在最初注册之后,心跳检测就没用了

经过代码审查,定位到两个核心缺陷。


根因分析

缺陷一:LastHeartBeat 只在健康检查成功时更新

文件src/.../Service/ServiceInfoService.cs:101

// 修改前
if (status != ServiceStatus.Unhealthy) service2.LastHeartBeat = DateTime.Now;

server 模式的健康检查失败(端口不通)时,状态被设为 Unhealthy,条件 status != UnhealthyfalseLastHeartBeat 不更新,始终停留在注册时间。管理后台看到"最后响应时间"从未变化,用户误以为健康检查没有运行。

缺陷二:健康检查后台循环可能静默崩溃

文件src/.../Service/ServiceHealthCheckService.cs:99-157

整个 while(true) 循环体没有任何 try-catch 保护,且通过 _ = ...StartCheckAsync() fire-and-forget 启动。如果 QueryAsync 等数据库操作抛出异常,整个后台任务静默终止,所有 server 模式服务永久失去心跳检测。此时即使端口开放,状态也永远不会恢复。


修改文件

文件 改动
ServiceInfoService.cs 移除 LastHeartBeat 更新的条件判断,每次检查后都更新时间戳
ServiceHealthCheckService.cs while 循环加 try-catch;CheckInterval 提前取值防 getter 异常;优化日志

效果对比

场景 修改前 修改后
端口未开放,健康检查失败 LastHeartBeat 冻结在注册时间 → 看起来"检查没在运行" LastHeartBeat 每次检查后更新 → 确认检查持续运行
端口开放后 若循环已崩溃则永远无法恢复,若未崩溃可恢复但时间戳迷惑 循环不会崩溃,下次检查即可恢复为 Healthy
检查日志 仅记录失败(down),成功静默 成功(up)和失败(down+状态码)都记录
removeServiceInterval > 0 LastHeartBeat 冻结可能导致服务被误删 时间戳持续更新,不会误删

风险

  • 极低。核心修改仅一行(删除条件判断),其余为 try-catch 和日志增强
  • LastHeartBeat 语义从"最后一次成功心跳"变为"最后一次检查时间",新语义更匹配管理后台"最后响应时间"的含义
  • 对 client 模式无影响

⚠️ 此 PR 由 AI(Claude Code)辅助生成和修改,请仔细审核后再合入。

1. LastHeartBeat 在每次健康检查后都更新,不再仅在Healthy时更新。
   修复前:健康检查失败时 LastHeartBeat 冻结在注册时间,管理后台看起来像
   心跳检测从未运行过,且 removeServiceInterval 可能因此误删服务。

2. 健康检查后台循环增加 try-catch 异常保护。
   修复前:while 循环中任何未捕获异常(如数据库查询失败)都会静默杀死
   整个健康检查任务,导致所有 server 模式服务永久失去心跳检测。

3. 优化:
   - CheckInterval 取值提前到循环外,防止 getter 抛异常杀循环
   - CheckAService 新增成功日志和失败时的 HTTP 状态码
   - Task.Run 内的健康检查任务增加独立异常保护
   - 启动时打印健康检查间隔,便于运维确认配置

Co-Authored-By: Claude <noreply@anthropic.com>
@kklldog

kklldog commented Jul 26, 2026

Copy link
Copy Markdown
Collaborator

最后响应时间,是说最后一次成功心跳的时间啊。

@animacaeli

Copy link
Copy Markdown
Author

我这边遇到的业务场景是服务注册成功,检测 url 中的端口未开放,状态为异常。端口开放后,状态也不会变为健康,就一直是异常状态。主要针对管理后台的显示问题

@kklldog

kklldog commented Jul 26, 2026

Copy link
Copy Markdown
Collaborator

我这边遇到的业务场景是服务注册成功,检测 url 中的端口未开放,状态为异常。端口开放后,状态也不会变为健康,就一直是异常状态。主要针对管理后台的显示问题

先看日志,确定健康监测是否在运行。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants