下面来说下eureka的自我保护机制。当每分钟心跳次数( renewsLastMin ) 小于 numberOfRenewsPerMinThreshold 时,并且开启自动保护模式开关( eureka.enableSelfPreservation = true ) 时,触发自动保护机制,不再自动过期租约
public void evict(long additionalLeaseMs) {
logger.debug("Running the evict task");
if (!isLeaseExpirationEnabled()) {
logger.debug("DS: lease expiration is currently disabled.");
return;
}
// 获得 所有过期的租约
// We collect first all expired items, to evict them in random order. For large eviction sets,
// if we do not that, we might wipe out whole apps before self preservation kicks in. By randomizing it,
// the impact should be evenly distributed across all applications.
List> expiredLeases = new ArrayList<>();
for (Entry>> groupEntry : registry.entrySet()) {
Map> leaseMap = groupEntry.getValue();
if (leaseMap != null) {
for (Entry> leaseEntry : leaseMap.entrySet()) {
Lease lease = leaseEntry.getValue();
if (lease.isExpired(additionalLeaseMs) && lease.getHolder() != null) { // 过期
expiredLeases.add(lease);
}
}
}
}
// 计算 最大允许清理租约数量
// To compensate for GC pauses or drifting local time, we need to use current registry size as a base for
// triggering self-preservation. Without that we would wipe out full registry.
int registrySize = (int) getLocalRegistrySize();
int registrySizeThreshold = (int) (registrySize * serverConfig.getRenewalPercentThreshold());
int evictionLimit = registrySize - registrySizeThreshold;
// 计算 清理租约数量
int toEvict = Math.min(expiredLeases.size(), evictionLimit);
if (toEvict > 0) {
logger.info("Evicting {} items (expired={}, evictionLimit={})", toEvict, expiredLeases.size(), evictionLimit);
// 逐个过期
Random random = new Random(System.currentTimeMillis());
for (int i = 0; i < toEvict; i++) {
// Pick a random item (Knuth shuffle algorithm)
int next = i + random.nextInt(expiredLeases.size() - i);
Collections.swap(expiredLeases, i, next);
Lease lease = expiredLeases.get(i);
String appName = lease.getHolder().getAppName();
String id = lease.getHolder().getId();
EXPIRED.increment();
logger.warn("DS: Registry: expired lease for {}/{}", appName, id);
internalCancel(appName, id, false);
}
}
}
EurekaServer 在启动时,从 EurekaServer集群获取注册信息,并首次初始化 numberOfRenewsPerMinThreshold 、 expectedNumberOfRenewsPerMin。Eureka-Server 定时重新计算 numberOfRenewsPerMinThreshold 、expectedNumberOfRenewsPerMin 。
private void scheduleRenewalThresholdUpdateTask() {
timer.schedule(new TimerTask() {
@Override
public void run() {
updateRenewalThreshold();
}
}, serverConfig.getRenewalThresholdUpdateIntervalMs(),
serverConfig.getRenewalThresholdUpdateIntervalMs());
}
private void updateRenewalThreshold() {
try {
// 计算 应用实例数
Applications apps = eurekaClient.getApplications();
int count = 0;
for (Application app : apps.getRegisteredApplications()) {
for (InstanceInfo instance : app.getInstances()) {
if (this.isRegisterable(instance)) {
++count;
}
}
}
// 计算 expectedNumberOfRenewsPerMin 、 numberOfRenewsPerMinThreshold 参数
synchronized (lock) {
// Update threshold only if the threshold is greater than the
// current expected threshold of if the self preservation is disabled.
if ((count * 2) > (serverConfig.getRenewalPercentThreshold() * numberOfRenewsPerMinThreshold)
|| (!this.isSelfPreservationModeEnabled())) {
this.expectedNumberOfRenewsPerMin = count * 2;
this.numberOfRenewsPerMinThreshold = (int) ((count * 2) * serverConfig.getRenewalPercentThreshold());
}
}
logger.info("Current renewal threshold is : {}", numberOfRenewsPerMinThreshold);
} catch (Throwable e) {
logger.error("Cannot update renewal threshold", e);
}
}
配置
eureka.renewalThresholdUpdateIntervalMs
参数,定时重新计算。默认,15 分钟。代码块
!this.isSelfPreservationModeEnabled()
:当未开启自我保护机制时,每次都进行重新计算。事实上,这两个参数不仅仅自我保护机制会使用到,配合 Netflix Servo实现监控信息采集numberOfRenewsPerMinThreshold
、expectedNumberOfRenewsPerMin
。-
代码块
(count * 2) > (serverConfig.getRenewalPercentThreshold() * numberOfRenewsPerMinThreshold)
:当开启自我保护机制时,应用实例每分钟最大心跳数(count * 2
) 小于期望最小每分钟续租次数(serverConfig.getRenewalPercentThreshold() * numberOfRenewsPerMinThreshold
),不重新计算。如果重新计算,自动保护机制会每次定时执行后失效。
应用实例注册时,增加 numberOfRenewsPerMinThreshold 、expectedNumberOfRenewsPerMin。public void register(InstanceInfo registrant, int leaseDuration, boolean isReplication) { try { // 获取读锁 read.lock(); Map
> gMap = registry.get(registrant.getAppName()); // 增加 注册次数 到 监控 REGISTER.increment(isReplication); // 获得 应用实例信息 对应的 租约 if (gMap == null) { final ConcurrentHashMap > gNewMap = new ConcurrentHashMap >(); gMap = registry.putIfAbsent(registrant.getAppName(), gNewMap); // 添加 应用 if (gMap == null) { // 添加 应用 成功 gMap = gNewMap; } } Lease existingLease = gMap.get(registrant.getId()); // Retain the last dirty timestamp without overwriting it, if there is already a lease if (existingLease != null && (existingLease.getHolder() != null)) { // 已存在时,使用数据不一致的时间大的应用注册信息为有效的 Long existingLastDirtyTimestamp = existingLease.getHolder().getLastDirtyTimestamp(); // Server 注册的 InstanceInfo Long registrationLastDirtyTimestamp = registrant.getLastDirtyTimestamp(); // Client 请求的 InstanceInfo logger.debug("Existing lease found (existing={}, provided={}", existingLastDirtyTimestamp, registrationLastDirtyTimestamp); // this is a > instead of a >= because if the timestamps are equal, we still take the remote transmitted // InstanceInfo instead of the server local copy. if (existingLastDirtyTimestamp > registrationLastDirtyTimestamp) { logger.warn("There is an existing lease and the existing lease's dirty timestamp {} is greater" + " than the one that is being registered {}", existingLastDirtyTimestamp, registrationLastDirtyTimestamp); logger.warn("Using the existing instanceInfo instead of the new instanceInfo as the registrant"); registrant = existingLease.getHolder(); } } else { // The lease does not exist and hence it is a new registration // 【自我保护机制】增加 `numberOfRenewsPerMinThreshold` 、`expectedNumberOfRenewsPerMin` synchronized (lock) { if (this.expectedNumberOfRenewsPerMin > 0) { // Since the client wants to cancel it, reduce the threshold // (1 // for 30 seconds, 2 for a minute) this.expectedNumberOfRenewsPerMin = this.expectedNumberOfRenewsPerMin + 2; this.numberOfRenewsPerMinThreshold = (int) (this.expectedNumberOfRenewsPerMin * serverConfig.getRenewalPercentThreshold()); } } logger.debug("No previous lease information found; it is new registration"); } // 创建 租约 Lease lease = new Lease (registrant, leaseDuration); if (existingLease != null) { // 若租约已存在,设置 租约的开始服务的时间戳 lease.setServiceUpTimestamp(existingLease.getServiceUpTimestamp()); } // 添加到 租约映射 gMap.put(registrant.getId(), lease); // 添加到 最近注册的调试队列 synchronized (recentRegisteredQueue) { recentRegisteredQueue.add(new Pair ( System.currentTimeMillis(), registrant.getAppName() + "(" + registrant.getId() + ")")); } // 添加到 应用实例覆盖状态映射(Eureka-Server 初始化使用) // This is where the initial state transfer of overridden status happens if (!InstanceStatus.UNKNOWN.equals(registrant.getOverriddenStatus())) { logger.debug("Found overridden status {} for instance {}. Checking to see if needs to be add to the " + "overrides", registrant.getOverriddenStatus(), registrant.getId()); if (!overriddenInstanceStatusMap.containsKey(registrant.getId())) { logger.info("Not found overridden id {} and hence adding it", registrant.getId()); overriddenInstanceStatusMap.put(registrant.getId(), registrant.getOverriddenStatus()); } } // 设置 应用实例覆盖状态 InstanceStatus overriddenStatusFromMap = overriddenInstanceStatusMap.get(registrant.getId()); if (overriddenStatusFromMap != null) { logger.info("Storing overridden status {} from map", overriddenStatusFromMap); registrant.setOverriddenStatus(overriddenStatusFromMap); } // 获得 应用实例状态 // Set the status based on the overridden status rules InstanceStatus overriddenInstanceStatus = getOverriddenInstanceStatus(registrant, existingLease, isReplication); // 设置 应用实例状态 registrant.setStatusWithoutDirty(overriddenInstanceStatus); // 设置 租约的开始服务的时间戳(只有第一次有效) // If the lease is registered with UP status, set lease service up timestamp if (InstanceStatus.UP.equals(registrant.getStatus())) { lease.serviceUp(); } // 设置 应用实例信息的操作类型 为 添加 registrant.setActionType(ActionType.ADDED); // 添加到 最近租约变更记录队列 recentlyChangedQueue.add(new RecentlyChangedItem(lease)); // 设置 租约的最后更新时间戳 registrant.setLastUpdatedTimestamp(); // 设置 响应缓存 过期 invalidateCache(registrant.getAppName(), registrant.getVIPAddress(), registrant.getSecureVipAddress()); logger.info("Registered instance {}/{} with status {} (replication={})", registrant.getAppName(), registrant.getId(), registrant.getStatus(), isReplication); } finally { // 释放锁 read.unlock(); } }
应用实例下线时,减少 numberOfRenewsPerMinThreshold 、expectedNumberOfRenewsPerMin。protected boolean internalCancel(String appName, String id, boolean isReplication) { try { // 获得读锁 read.lock(); // 增加 取消注册次数 到 监控 CANCEL.increment(isReplication); // 移除 租约映射 Map
> gMap = registry.get(appName); Lease leaseToCancel = null; if (gMap != null) { leaseToCancel = gMap.remove(id); } // 添加到 最近取消注册的调试队列 synchronized (recentCanceledQueue) { recentCanceledQueue.add(new Pair (System.currentTimeMillis(), appName + "(" + id + ")")); } // 移除 应用实例覆盖状态映射 InstanceStatus instanceStatus = overriddenInstanceStatusMap.remove(id); if (instanceStatus != null) { logger.debug("Removed instance id {} from the overridden map which has value {}", id, instanceStatus.name()); } // 租约不存在 if (leaseToCancel == null) { CANCEL_NOT_FOUND.increment(isReplication); // 添加 取消注册不存在 到 监控 logger.warn("DS: Registry: cancel failed because Lease is not registered for: {}/{}", appName, id); return false; // 失败 } else { // 设置 租约的取消注册时间戳 leaseToCancel.cancel(); // 添加到 最近租约变更记录队列 InstanceInfo instanceInfo = leaseToCancel.getHolder(); String vip = null; String svip = null; if (instanceInfo != null) { instanceInfo.setActionType(ActionType.DELETED); recentlyChangedQueue.add(new RecentlyChangedItem(leaseToCancel)); instanceInfo.setLastUpdatedTimestamp(); vip = instanceInfo.getVIPAddress(); svip = instanceInfo.getSecureVipAddress(); } // 设置 响应缓存 过期 invalidateCache(appName, vip, svip); logger.info("Cancelled instance {}/{} (replication={})", appName, id, isReplication); return true; // 成功 } } finally { // 释放锁 read.unlock(); } }
eureka的自我保护机制就介绍到这里了。