跳到主要内容

健康检查机制

健康检查是 Nacos 保证服务可用性的核心机制。本章深入介绍 Nacos 的健康检查原理和配置方法。

健康检查概述

在微服务架构中,服务实例可能因为各种原因变得不可用:进程崩溃、网络故障、资源耗尽等。健康检查机制能够及时发现这些问题,将不健康的实例从服务列表中剔除,防止请求被发送到不可用的实例。

Nacos 健康检查模式

Nacos 支持两种健康检查模式,分别对应两种实例类型:

模式实例类型检查方式适用场景
客户端心跳临时实例(Ephemeral)客户端主动发送心跳微服务实例
服务端探测持久实例(Persistent)服务端主动探测数据库、中间件等

临时实例健康检查

临时实例采用客户端心跳模式,这是微服务场景下最常用的模式。

工作原理

┌──────────────────────────────────────────────────────────────────┐
│ 临时实例健康检查流程 │
│ │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ 服务实例 │ │ Nacos │ │
│ │ (客户端) │ │ Server │ │
│ └──────────────┘ └──────────────┘ │
│ │ │ │
│ │ 1. 注册实例 │ │
│ │ ─────────────────────────────────>│ │
│ │ │ │
│ │ 2. 启动心跳任务 │ │
│ │ (每 5 秒发送一次) │ │
│ │ ─────────────────────────────────>│ │
│ │ │ │
│ │ 3. 心跳响应 │ │
│ │<───────────────────────────────── │ │
│ │ │ │
│ │ │ 15 秒无心跳 │
│ │ │ → 标记为不健康 │
│ │ │ │
│ │ │ 30 秒无心跳 │
│ │ │ → 剔除实例 │
└──────────────────────────────────────────────────────────────────┘

心跳时间配置

spring:
cloud:
nacos:
discovery:
# 心跳间隔(毫秒),默认 5000
heart-beat-interval: 5000
# 心跳超时时间(毫秒),默认 15000
heart-beat-timeout: 15000
# IP 删除超时时间(毫秒),默认 30000
ip-delete-timeout: 30000

时间参数关系

心跳间隔 < 心跳超时 < IP 删除超时

心跳间隔:5 秒 → 客户端每 5 秒发送一次心跳
心跳超时:15 秒 → 15 秒未收到心跳,标记为不健康
IP 删除超时:30 秒 → 30 秒未收到心跳,从列表中剔除

实例状态流转

┌─────────────┐     注册成功      ┌─────────────┐
│ 未注册 │ ───────────────> │ 健康 │
└─────────────┘ └─────────────┘

│ 15 秒无心跳

┌─────────────┐
│ 不健康 │
└─────────────┘

│ 30 秒无心跳

┌─────────────┐
│ 已剔除 │
└─────────────┘

心跳实现原理

Spring Cloud Alibaba Nacos Discovery 使用定时任务发送心跳:

// 简化的心跳逻辑
public class BeatTask implements Runnable {

@Override
public void run() {
// 构建心跳请求
BeatInfo beatInfo = new BeatInfo();
beatInfo.setServiceName(serviceName);
beatInfo.setIp(ip);
beatInfo.setPort(port);

// 发送心跳
JSONObject result = namingService.sendBeat(beatInfo);

// 处理响应
if (result != null) {
long nextBeatDelay = result.getLong("clientBeatInterval");
// 重新调度下一次心跳
executorService.schedule(new BeatTask(), nextBeatDelay, TimeUnit.MILLISECONDS);
}
}
}

持久实例健康检查

持久实例由 Nacos Server 主动探测,适合需要长期稳定存在的服务。

支持的探测方式

探测方式说明配置参数
TCP检测端口是否可连接默认方式
HTTP发送 HTTP 请求检查响应配置健康检查 URL
MySQL检测 MySQL 是否可用自动识别
自定义用户自定义探测逻辑实现 HealthCheckProcessor

TCP 探测

TCP 探测是最简单的方式,只需检测端口是否可连接:

# 注册持久实例
curl -X POST 'http://127.0.0.1:8848/nacos/v1/ns/instance' \
-d 'serviceName=mysql-service' \
-d 'ip=192.168.1.200' \
-d 'port=3306' \
-d 'ephemeral=false'

Nacos 会定期尝试连接 192.168.1.200:3306,如果连接失败则标记为不健康。

HTTP 探测

通过配置健康检查 URL,Nacos 会发送 HTTP 请求进行探测:

# 注册时配置健康检查 URL
curl -X POST 'http://127.0.0.1:8848/nacos/v1/ns/instance' \
-d 'serviceName=web-service' \
-d 'ip=192.168.1.100' \
-d 'port=8080' \
-d 'ephemeral=false' \
-d 'metadata={"preserved.register.source":"SPRING_CLOUD"}'

在 Spring Boot 应用中实现健康检查端点:

@RestController
public class HealthController {

@GetMapping("/actuator/health")
public Map<String, Object> health() {
Map<String, Object> result = new HashMap<>();

// 检查各项健康指标
boolean dbHealthy = checkDatabase();
boolean redisHealthy = checkRedis();

if (dbHealthy && redisHealthy) {
result.put("status", "UP");
} else {
result.put("status", "DOWN");
result.put("details", Map.of(
"database", dbHealthy ? "UP" : "DOWN",
"redis", redisHealthy ? "UP" : "DOWN"
));
}

return result;
}

private boolean checkDatabase() {
// 检查数据库连接
try {
// 执行简单查询
return true;
} catch (Exception e) {
return false;
}
}

private boolean checkRedis() {
// 检查 Redis 连接
try {
// 执行 PING 命令
return true;
} catch (Exception e) {
return false;
}
}
}

探测配置

在 Nacos Server 端配置探测参数:

# 健康检查间隔(毫秒)
nacos.naming.health-check.interval=5000

# 健康检查超时时间(毫秒)
nacos.naming.health-check.timeout=3000

# 健康检查重试次数
nacos.naming.health-check.retry=3

健康检查 API

查询实例健康状态

# 查询服务实例列表(只返回健康实例)
curl 'http://127.0.0.1:8848/nacos/v1/ns/instance/list?serviceName=user-service'

# 查询所有实例(包括不健康实例)
curl 'http://127.0.0.1:8848/nacos/v1/ns/instance/list?serviceName=user-service&healthyOnly=false'

# 查询指定实例详情
curl 'http://127.0.0.1:8848/nacos/v1/ns/instance?serviceName=user-service&ip=192.168.1.100&port=8080'

手动更新实例健康状态

# 标记实例为不健康
curl -X PUT 'http://127.0.0.1:8848/nacos/v1/ns/instance' \
-d 'serviceName=user-service' \
-d 'ip=192.168.1.100' \
-d 'port=8080' \
-d 'healthy=false'

发送心跳

# 手动发送心跳
curl -X PUT 'http://127.0.0.1:8848/nacos/v1/ns/instance/beat' \
-d 'serviceName=user-service' \
-d 'ip=192.168.1.100' \
-d 'port=8080' \
-d 'beat={"cluster":"DEFAULT","ip":"192.168.1.100","port":8080,"serviceName":"user-service"}'

保护阈值

保护阈值是防止服务雪崩的重要机制。

雪崩问题

假设某服务有 10 个实例,其中 8 个因故障变为不健康状态。如果没有保护机制,所有请求都会涌向剩余的 2 个健康实例,可能导致这 2 个实例也被压垮,形成雪崩效应。

保护阈值原理

健康实例比例 = 健康实例数 / 总实例数

如果 健康实例比例 < 保护阈值:
返回所有实例(包括不健康的)
否则:
只返回健康实例

配置保护阈值

spring:
cloud:
nacos:
discovery:
# 保护阈值,范围 0-1
protect-threshold: 0.5

或在控制台配置:

  1. 进入「服务管理」->「服务列表」
  2. 点击服务名进入详情
  3. 设置「保护阈值」

保护阈值示例

假设服务有 10 个实例,保护阈值设为 0.5:

健康实例数健康比例保护阈值返回结果
10100%50%10 个健康实例
660%50%6 个健康实例
440%50%所有 10 个实例
220%50%所有 10 个实例

当健康比例低于阈值时,返回所有实例,让消费者自行决定如何处理。

健康检查最佳实践

1. 合理设置心跳参数

根据网络环境和业务特点调整心跳参数:

# 网络稳定的环境
spring:
cloud:
nacos:
discovery:
heart-beat-interval: 5000
heart-beat-timeout: 15000
ip-delete-timeout: 30000

# 网络不稳定的环境
spring:
cloud:
nacos:
discovery:
heart-beat-interval: 10000
heart-beat-timeout: 30000
ip-delete-timeout: 60000

2. 实现完善的健康检查端点

健康检查端点应该检查所有关键依赖:

@RestController
public class HealthController {

@Autowired
private DataSource dataSource;

@Autowired
private RedisTemplate<String, String> redisTemplate;

@GetMapping("/health")
public ResponseEntity<Map<String, Object>> health() {
Map<String, Object> health = new LinkedHashMap<>();
Map<String, Object> components = new LinkedHashMap<>();

// 检查数据库
components.put("database", checkDatabase());

// 检查 Redis
components.put("redis", checkRedis());

// 检查磁盘空间
components.put("diskSpace", checkDiskSpace());

// 判断整体状态
boolean allHealthy = components.values().stream()
.allMatch(status -> "UP".equals(((Map<?, ?>) status).get("status")));

health.put("status", allHealthy ? "UP" : "DOWN");
health.put("components", components);
health.put("timestamp", Instant.now());

return allHealthy
? ResponseEntity.ok(health)
: ResponseEntity.status(503).body(health);
}

private Map<String, Object> checkDatabase() {
Map<String, Object> result = new HashMap<>();
try (Connection conn = dataSource.getConnection()) {
boolean valid = conn.isValid(3);
result.put("status", valid ? "UP" : "DOWN");
} catch (Exception e) {
result.put("status", "DOWN");
result.put("error", e.getMessage());
}
return result;
}

private Map<String, Object> checkRedis() {
Map<String, Object> result = new HashMap<>();
try {
String pong = redisTemplate.getConnectionFactory()
.getConnection().ping();
result.put("status", "PONG".equals(pong) ? "UP" : "DOWN");
} catch (Exception e) {
result.put("status", "DOWN");
result.put("error", e.getMessage());
}
return result;
}

private Map<String, Object> checkDiskSpace() {
Map<String, Object> result = new HashMap<>();
File file = new File("/");
long freeSpace = file.getFreeSpace();
long totalSpace = file.getTotalSpace();
double freePercent = (double) freeSpace / totalSpace * 100;

result.put("status", freePercent > 10 ? "UP" : "DOWN");
result.put("freePercent", String.format("%.2f%%", freePercent));
return result;
}
}

3. 使用 Spring Boot Actuator

Spring Boot Actuator 提供了完善的健康检查支持:

# application.yml
management:
endpoints:
web:
exposure:
include: health,info
endpoint:
health:
show-details: always

添加依赖后,Actuator 会自动检查常见组件的健康状态:

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

4. 合理设置保护阈值

根据业务特点设置保护阈值:

  • 高可用服务:设置较低的阈值(如 0.3),优先保证服务可用
  • 数据一致性要求高的服务:设置较高的阈值(如 0.7),优先保证数据正确

5. 监控告警

配置健康状态监控告警:

@Component
public class HealthStatusMonitor {

@Autowired
private NamingService namingService;

@Scheduled(fixedRate = 60000)
public void checkServiceHealth() {
try {
List<Instance> instances = namingService.selectInstances(
"user-service",
false // 获取所有实例
);

long unhealthyCount = instances.stream()
.filter(i -> !i.isHealthy())
.count();

if (unhealthyCount > 0) {
// 发送告警
sendAlert(String.format(
"服务 user-service 有 %d 个不健康实例",
unhealthyCount
));
}
} catch (NacosException e) {
log.error("检查服务健康状态失败", e);
}
}

private void sendAlert(String message) {
// 实现告警逻辑:邮件、短信、钉钉等
}
}

常见问题

1. 实例频繁标记为不健康

可能原因:

  • 网络延迟高,心跳超时
  • 应用 GC 时间过长,阻塞心跳线程
  • Nacos Server 负载过高

解决方案:

  • 增加心跳超时时间
  • 优化 JVM 参数减少 GC 时间
  • 扩容 Nacos 集群

2. 实例被错误剔除

可能原因:

  • 应用启动慢,心跳未及时发送
  • 网络抖动导致心跳丢失

解决方案:

  • 增加删除超时时间
  • 应用启动完成后延迟注册

3. 健康检查端点响应慢

可能原因:

  • 健康检查逻辑复杂
  • 依赖服务响应慢

解决方案:

  • 简化健康检查逻辑
  • 设置超时时间
  • 使用缓存避免频繁检查

下一步