核心概念
自我保护机制是服务注册中心的一种容错机制,当网络出现异常或服务大量下线时,防止注册中心误删健康的服务实例。Nacos的自我保护机制主要针对临时实例(AP模式),在心跳失败率超过阈值时触发。
核心思想:宁可保留可能不健康的实例,也不删除可能健康的实例,保证服务的高可用性。
自我保护触发条件
1. 心跳失败率阈值
// Nacos Server配置
nacos:
naming:
# 自我保护阈值:心跳失败率超过此值触发保护
protect-threshold: 0.85 # 默认0.85,即85%
# 健康检查任务执行间隔
health-check-interval: 5000 # 5秒
触发条件:
- 计算所有临时实例的心跳失败率
- 如果失败率 >
protect-threshold(默认85%),触发自我保护 - 自我保护期间,不再自动删除实例
2. 心跳失败率计算
// ServiceManager.java - 计算心跳失败率
public class ServiceManager {
private static final double DEFAULT_PROTECT_THRESHOLD = 0.85;
/**
* 计算服务的心跳失败率
*/
public double getHeartBeatFailRate(Service service) {
int totalInstances = 0;
int failInstances = 0;
for (Cluster cluster : service.getClusterMap().values()) {
Set<Instance> instances = cluster.getEphemeralInstances();
for (Instance instance : instances) {
totalInstances++;
// 检查是否心跳超时
long currentTime = System.currentTimeMillis();
long lastBeat = instance.getLastBeat();
long heartBeatTimeout = instance.getHeartBeatTimeout();
if (currentTime - lastBeat > heartBeatTimeout) {
// 心跳超时,计入失败
failInstances++;
}
}
}
if (totalInstances == 0) {
return 0.0;
}
// 失败率 = 失败实例数 / 总实例数
return (double) failInstances / totalInstances;
}
/**
* 判断是否触发自我保护
*/
public boolean isProtectEnabled(Service service) {
double protectThreshold = getProtectThreshold(service);
double failRate = getHeartBeatFailRate(service);
// 失败率超过阈值,触发自我保护
return failRate >= protectThreshold;
}
}
自我保护机制实现
1. 健康检查任务改造
// ServiceManager.java - 健康检查任务(带自我保护)
private void healthCheck() {
for (Service service : getAllServices()) {
// 1. 检查是否触发自我保护
boolean protectEnabled = isProtectEnabled(service);
if (protectEnabled) {
// 自我保护模式:只标记不健康,不删除实例
log.warn("Service {} is in protect mode, fail rate: {}",
service.getName(), getHeartBeatFailRate(service));
// 只标记不健康,不删除
markUnhealthyInstances(service);
} else {
// 正常模式:标记不健康并删除超时实例
markUnhealthyInstances(service);
removeTimeoutInstances(service);
}
}
}
/**
* 标记不健康的实例
*/
private void markUnhealthyInstances(Service service) {
for (Cluster cluster : service.getClusterMap().values()) {
Set<Instance> instances = cluster.getEphemeralInstances();
for (Instance instance : instances) {
long currentTime = System.currentTimeMillis();
long lastBeat = instance.getLastBeat();
long heartBeatTimeout = instance.getHeartBeatTimeout();
// 心跳超时,标记不健康
if (currentTime - lastBeat > heartBeatTimeout) {
if (instance.isHealthy()) {
instance.setHealthy(false);
log.warn("Instance {} marked as unhealthy", instance);
// 触发健康状态变更事件
NotifyCenter.publishEvent(new InstanceHealthStatusEvent(instance));
}
} else {
// 心跳正常,恢复健康
if (!instance.isHealthy()) {
instance.setHealthy(true);
log.info("Instance {} recovered to healthy", instance);
}
}
}
}
}
/**
* 删除超时实例(仅在非自我保护模式下执行)
*/
private void removeTimeoutInstances(Service service) {
for (Cluster cluster : service.getClusterMap().values()) {
Set<Instance> instances = cluster.getEphemeralInstances();
List<Instance> toRemove = new ArrayList<>();
for (Instance instance : instances) {
long currentTime = System.currentTimeMillis();
long lastBeat = instance.getLastBeat();
long ipDeleteTimeout = instance.getIpDeleteTimeout();
// 超过删除超时时间,加入删除列表
if (currentTime - lastBeat > ipDeleteTimeout) {
toRemove.add(instance);
}
}
// 删除超时实例
for (Instance instance : toRemove) {
removeInstance(service, instance);
log.warn("Instance {} removed due to timeout", instance);
}
}
}
2. 自我保护状态管理
// ServiceManager.java - 自我保护状态
public class ServiceManager {
// 服务级别的自我保护状态
private final Map<String, Boolean> protectStatusMap = new ConcurrentHashMap<>();
/**
* 获取服务的自我保护状态
*/
public boolean isServiceProtectEnabled(String namespaceId, String serviceName) {
String key = buildKey(namespaceId, serviceName);
return protectStatusMap.getOrDefault(key, false);
}
/**
* 更新自我保护状态
*/
private void updateProtectStatus(Service service) {
String key = buildKey(service.getNamespaceId(), service.getName());
boolean protectEnabled = isProtectEnabled(service);
boolean oldStatus = protectStatusMap.getOrDefault(key, false);
protectStatusMap.put(key, protectEnabled);
// 状态变更时记录日志
if (oldStatus != protectEnabled) {
if (protectEnabled) {
log.warn("Service {} entered protect mode, fail rate: {}",
service.getName(), getHeartBeatFailRate(service));
} else {
log.info("Service {} exited protect mode", service.getName());
}
}
}
}
自我保护触发场景
1. 网络分区
场景:Nacos Server与部分Client网络不通
正常Client: 心跳正常 ✅
异常Client: 心跳失败 ❌
结果:
- 心跳失败率 > 85%
- 触发自我保护
- 异常Client的实例不会被删除
- 网络恢复后,实例自动恢复
2. 服务大量下线
场景:服务实例批量重启或故障
正常实例: 100个
故障实例: 90个
心跳失败率 = 90 / 100 = 90% > 85%
结果:
- 触发自我保护
- 故障实例不会被删除
- 等待服务恢复或手动处理
3. Nacos Server负载过高
场景:Nacos Server处理心跳请求变慢
Client发送心跳 → Server处理延迟 → 心跳超时
结果:
- 大量心跳被认为超时
- 心跳失败率上升
- 触发自我保护
- 防止误删健康实例
自我保护配置
1. 服务级别配置
// 通过元数据配置服务的自我保护阈值
spring:
cloud:
nacos:
discovery:
metadata:
# 自定义自我保护阈值(0-1之间)
protect.threshold: 0.8
2. 全局配置
# Nacos Server配置文件 application.properties
# 全局自我保护阈值
nacos.naming.protect.threshold=0.85
# 是否启用自我保护(默认true)
nacos.naming.protect.enabled=true
3. 动态配置
// 通过Nacos控制台或API动态调整
@RestController
@RequestMapping("/nacos/admin")
public class NacosAdminController {
@PostMapping("/protect/threshold")
public String setProtectThreshold(@RequestParam String serviceName,
@RequestParam double threshold) {
// 动态设置服务的自我保护阈值
serviceManager.setProtectThreshold(serviceName, threshold);
return "ok";
}
@PostMapping("/protect/enable")
public String enableProtect(@RequestParam String serviceName,
@RequestParam boolean enabled) {
// 动态启用/禁用自我保护
serviceManager.setProtectEnabled(serviceName, enabled);
return "ok";
}
}
自我保护 vs Eureka自我保护
1. 触发条件对比
| 维度 | Nacos | Eureka |
|---|---|---|
| 触发条件 | 心跳失败率 > 85% | 15分钟内续约失败率 > 15% |
| 计算方式 | 实时计算 | 15分钟窗口 |
| 阈值配置 | 可配置(默认85%) | 固定15% |
| 作用范围 | 服务级别 | 全局 |
2. 保护策略对比
Nacos自我保护:
// 服务级别保护
if (isProtectEnabled(service)) {
// 只标记不健康,不删除
markUnhealthyInstances(service);
} else {
// 正常删除超时实例
removeTimeoutInstances(service);
}
Eureka自我保护:
// 全局保护
if (isSelfPreservationModeEnabled()) {
// 停止所有实例的过期删除
// 但会继续标记不健康
return; // 不执行删除逻辑
}
3. 恢复机制对比
Nacos:
- 心跳失败率降低后,自动退出自我保护
- 恢复正常删除逻辑
- 恢复速度更快(实时计算)
Eureka:
- 15分钟内续约成功率恢复后,自动退出
- 需要等待15分钟窗口
- 恢复速度较慢
性能优化与监控
1. 自我保护状态缓存
// 缓存自我保护状态,减少重复计算
public class ServiceManager {
private final Map<String, CachedProtectStatus> protectStatusCache = new ConcurrentHashMap<>();
private static class CachedProtectStatus {
private boolean protectEnabled;
private long lastUpdateTime;
private static final long CACHE_TTL = 5000; // 5秒缓存
public boolean isValid() {
return System.currentTimeMillis() - lastUpdateTime < CACHE_TTL;
}
}
public boolean isProtectEnabled(Service service) {
String key = buildKey(service);
CachedProtectStatus cached = protectStatusCache.get(key);
if (cached != null && cached.isValid()) {
return cached.protectEnabled;
}
// 重新计算
boolean protectEnabled = calculateProtectStatus(service);
protectStatusCache.put(key, new CachedProtectStatus(protectEnabled));
return protectEnabled;
}
}
2. 自我保护监控指标
// 暴露自我保护相关指标
public class ProtectMetrics {
private final MeterRegistry meterRegistry;
public void recordProtectStatus(Service service, boolean protectEnabled, double failRate) {
// 记录自我保护状态
meterRegistry.gauge("nacos.protect.enabled",
Tags.of("service", service.getName()),
protectEnabled ? 1 : 0
);
// 记录心跳失败率
meterRegistry.gauge("nacos.heartbeat.fail.rate",
Tags.of("service", service.getName()),
failRate
);
// 记录自我保护触发次数
if (protectEnabled) {
meterRegistry.counter("nacos.protect.triggered",
"service", service.getName()
).increment();
}
}
}
3. 自我保护告警
// 自我保护触发时发送告警
public class ProtectAlertService {
public void checkAndAlert(Service service) {
boolean protectEnabled = serviceManager.isProtectEnabled(service);
double failRate = serviceManager.getHeartBeatFailRate(service);
if (protectEnabled) {
// 发送告警
String message = String.format(
"Service %s entered protect mode, fail rate: %.2f%%",
service.getName(), failRate * 100
);
alertService.sendAlert("NACOS_PROTECT", message, AlertLevel.WARNING);
}
}
}
分布式场景考量
1. 集群环境下的自我保护
// 集群环境下,各节点独立计算自我保护状态
public class ServiceManager {
public void healthCheck() {
// 每个节点独立计算
boolean protectEnabled = isProtectEnabled(service);
// 自我保护状态通过Distro协议同步(AP模式)
if (protectEnabled) {
// 同步自我保护状态到其他节点
syncProtectStatus(service, protectEnabled);
}
}
private void syncProtectStatus(Service service, boolean protectEnabled) {
// 通过Distro协议同步到其他节点
for (Member member : clusterMembers) {
if (!member.isSelf()) {
distroProtocol.sync(new ProtectStatusEvent(service, protectEnabled), member);
}
}
}
}
2. 自我保护与数据一致性
AP模式(临时实例):
- 各节点独立计算自我保护状态
- 可能出现节点A触发保护,节点B未触发的情况
- 通过Distro协议最终一致
CP模式(持久化实例):
- 持久化实例不适用自我保护机制
- 健康检查结果通过Raft协议强一致
实战示例
1. 配置自我保护阈值
# Nacos Server配置
nacos:
naming:
protect-threshold: 0.85 # 85%心跳失败率触发保护
protect-enabled: true # 启用自我保护
2. 监控自我保护状态
@RestController
@RequestMapping("/nacos/monitor")
public class ProtectMonitorController {
@Autowired
private ServiceManager serviceManager;
@GetMapping("/protect/status")
public Map<String, Object> getProtectStatus() {
Map<String, Object> result = new HashMap<>();
for (Service service : serviceManager.getAllServices()) {
boolean protectEnabled = serviceManager.isProtectEnabled(service);
double failRate = serviceManager.getHeartBeatFailRate(service);
Map<String, Object> serviceStatus = new HashMap<>();
serviceStatus.put("protectEnabled", protectEnabled);
serviceStatus.put("failRate", failRate);
serviceStatus.put("totalInstances", service.getTotalInstanceCount());
serviceStatus.put("unhealthyInstances", service.getUnhealthyInstanceCount());
result.put(service.getName(), serviceStatus);
}
return result;
}
}
3. 手动退出自我保护
// 紧急情况下,手动退出自我保护
@PostMapping("/protect/disable")
public String disableProtect(@RequestParam String serviceName) {
serviceManager.setProtectEnabled(serviceName, false);
log.warn("Manually disabled protect for service: {}", serviceName);
return "ok";
}
面试总结
Nacos自我保护机制核心要点:
- 触发条件:
- 心跳失败率 > 85%(可配置)
- 实时计算,服务级别保护
- 保护策略:
- 自我保护期间,只标记不健康,不删除实例
- 防止网络分区或Server负载过高时误删健康实例
- 恢复机制:
- 心跳失败率降低后,自动退出自我保护
- 恢复正常删除逻辑
- 与Eureka对比:
- Nacos:服务级别,实时计算,阈值可配置
- Eureka:全局级别,15分钟窗口,固定15%阈值
- 适用场景:
- 网络分区
- 服务批量重启
- Nacos Server负载过高
技术亮点:
- 服务级别的细粒度保护
- 实时计算,响应更快
- 可配置阈值,灵活适配不同场景
- 完善的监控和告警机制
注意事项:
- 自我保护期间,不健康实例不会被删除,需要手动处理
- 建议配置监控告警,及时发现自我保护触发
- 根据实际场景调整阈值,平衡可用性和准确性