尧图精选

Spring Cloud Eureka 续约与剔除机制的深层源码

🕒 发布时间:2026/9/20 13:43:56 📁 来源:尧图网络
Spring Cloud Eureka 续约与剔除机制的深层源码在 Spring Cloud Netflix 技术栈维护中Eureka 注册中心是许多老牌微服务系统的核心命脉。但在日常运维中开发人员经常遇到两类极为折磨的故障下线迟钝某台微服务实例已经停机或崩溃上游网关和消费端却依然在 12 分钟内持续将流量打到死节点上引发大面积500 Connection Refused。自我保护误杀/僵死Eureka 控制台突发红色大字警告EMERGENCY! EUREKA MAY BE INCORRECTLY CLAIMING INSTANCES ARE UP WHEN THEYRE NOT此时挂掉的服务永远不会被踢掉导致脏路由长期残留。要彻底掌控 Eureka 的服务上下线行为必须从其底层的客户端心跳、服务端租约Lease、剔除定时任务EvictionTask以及三级缓存体系的源码实现切入。Eureka 续约与剔除核心时序拓扑[ Client: DiscoveryClient ] [ Server: InstanceRegistry ] │ │ │── 1. 定时心跳 (默认 30s) ─────────────────────│ renew() 更新 Lease.lastUpdateTimestamp │ │ │ │── 2. EvictionTask (默认 60s 轮询) │ │ ├── 校验是否开启自我保护 │ │ └── 检查 lease.isExpired() 90s │ │ └── internalCancel() 物理剔除 │ │ │── 3. 定时拉取注册表 (默认 30s) ───────────────│ readOnlyCacheMap (30s 刷新一次)客户端续约机制源码追踪微服务启动后DiscoveryClient.initScheduledTasks()会初始化一个固定周期的定时线程HeartbeatThread// DiscoveryClient.java private void initScheduledTasks() { // 默认 30 秒执行一次心跳 scheduler.schedule( new TimedSupervisorTask( heartbeat, scheduler, heartbeatExecutor, renewalIntervalInSecs, // 默认 30s TimeUnit.SECONDS, expBackOffBound, new HeartbeatThread() ), renewalIntervalInSecs, TimeUnit.SECONDS); }HeartbeatThread内部调用renew()方法向 Eureka Server 发起 HTTP PUT 请求eureka/apps/{appName}/{instanceId}?statusUPlastDirtyTimestamp...。服务端租约更新与自我保护判定Eureka Server 收到续约请求后交由AbstractInstanceRegistry.renew()处理// AbstractInstanceRegistry.java public boolean renew(String appName, String id, boolean isReplication) { // 1. 获取对应的租约对象 MapString, LeaseInstanceInfo gMap registry.get(appName); LeaseInstanceInfo leaseToRenew gMap.get(id); if (leaseToRenew null) { return false; // 租约不存在提示客户端重新 register } // 2. 刷新租约的最后更新时间戳 leaseToRenew.renew(); // 3. 统计上一分钟续约次数 (用于自我保护阈值判断) renewsLastMin.increment(); return true; }在Lease类中更新动作非常简单// Lease.java public void renew() { this.lastUpdateTimestamp System.currentTimeMillis() duration; } public boolean isExpired(long additionalWindowTime) { return (evictionTimestamp 0 || System.currentTimeMillis() (lastUpdateTimestamp duration additionalWindowTime)); }服务端剔除机制与自我保护算法1. 剔除任务EvictionTask服务端通过后台定时线程EvictionTask默认每 60 秒运行一次执行evict()扫描全量注册表// AbstractInstanceRegistry.java public void evict(long additionalWindowTime) { // 1. 判断是否触发了自我保护机制 if (!isLeaseExpirationEnabled()) { logger.error(Eureka is in self-preservation mode, eviction is disabled.); return; // 一旦处于自我保护状态直接跳过剔除一条都不删 } // 2. 遍历注册表收集所有超期的租约 (Lease) ListLeaseInstanceInfo expiredLeases new ArrayList(); for (EntryString, MapString, LeaseInstanceInfo groupEntry : registry.entrySet()) { MapString, LeaseInstanceInfo leasedMap groupEntry.getValue(); for (EntryString, LeaseInstanceInfo leaseEntry : leasedMap.entrySet()) { LeaseInstanceInfo lease leaseEntry.getValue(); if (lease.isExpired(additionalWindowTime) lease.getHolder() ! null) { expiredLeases.add(lease); } } } // 3. 限制单次剔除的最大数量 (补偿因子防止瞬间清空注册表) int registrySize (int) getLocalRegistrySize(); int registrySizeThreshold (int) (registrySize * serverConfig.getRenewalPercentThreshold()); int evictionLimit registrySize - registrySizeThreshold; int toEvict Math.min(expiredLeases.size(), evictionLimit); // 4. 随机洗牌剔除避免同批次聚集 for (int i 0; i toEvict; i) { int next i RND.nextInt(expiredLeases.size() - i); Collections.swap(expiredLeases, i, next); LeaseInstanceInfo lease expiredLeases.get(i); internalCancel(appName, id, false); // 执行物理剔除并从只读缓存逐出 } }2. 自我保护的数学判据Eureka 统计每分钟的心跳总数。如果在一分钟内收到的实际续约次数renewsLastMin低于理论阈值numberOfRenewsPerMinThresholdEureka 就会认为当前是自身机房网络故障或跨机房网络分区而不是下游服务真的挂了。阈值计算公式$$\text{每分钟预期心跳数} \text{实例数} \times \left(\frac{60}{\text{leaseRenewalIntervalInSeconds}}\right)$$$$\text{自我保护触发阈值} \text{每分钟预期心跳数} \times \text{renewalPercentThreshold} \quad (\text{默认 } 0.85)$$例如系统有 10 个实例默认 30 秒一次心跳每分钟理论应收到 $10 \times 2 20$ 次心跳。阈值为 $20 \times 0.85 17$ 次。如果某分钟收到的心跳数 $ 17$自我保护立即激活全量停止实例剔除。为什么感知下线最多需要 150 秒在默认配置下一个服务异常下线上游消费端感知到变更的总耗时是由多层缓存和轮询周期叠加决定的[ 心跳超时 90s ] [ 剔除轮询 60s ] [ 服务端只读缓存刷新 30s ] [ 客户端拉取周期 30s ] ≈ 150s210s租约超期判定客户端断电后需经历lease-expiration-duration-in-seconds默认 90s才被标记为 Expired。剔除轮询间隔eviction-interval-timer-in-ms默认 60s 运行一次。服务端三级缓存同步Eureka Server 读写缓存readWriteCacheMap向只读缓存readOnlyCacheMap同步的定时任务默认每 30s 一次response-cache-update-interval-ms。客户端本地缓存刷新客户端定时拉取registry-fetch-interval-seconds默认每 30s 一次。生产级快速下线优化配置在对发布感知度要求高的高频发布环境中可以通过调优参数将感知延迟从 150 秒压缩至58 秒Eureka Server 端优化配置eureka: server: # 关闭自我保护模式 (针对小规模内部稳定集群避免死节点残留) enable-self-preservation: false # 剔除任务扫描周期调为 3 秒 (默认 60s) eviction-interval-timer-in-ms: 3000 # 关闭只读缓存直接读取 LoadingCache 读写缓存彻底消除 30s 同步时延 use-read-only-response-cache: falseEureka Client 端优化配置eureka: instance: # 客户端心跳发送间隔缩减为 3 秒 (默认 30s) lease-renewal-interval-in-seconds: 3 # 服务端若 9 秒未收到心跳即判定租约过期 (默认 90s) lease-expiration-duration-in-seconds: 9 client: # 客户端拉取注册表增量周期间隔缩减为 3 秒 (默认 30s) registry-fetch-interval-seconds: 3结合这套配置当容器被销毁时Lease 在 9 秒内判定超时3 秒内被 EvictionTask 物理剔除消费端在 3 秒内完成增量拉取全链路在 10 秒左右即可彻底摘除故障流量。
上一篇/下一篇内容由系统自动关联 返回资讯列表 →