Eureka是一個開源的服務治理框架,它提供了完成的Service Registry和Service Discovery實現,並且和Spring Cloud無縫集成,使用Spring Boot + Spring Cloud可以輕鬆的將註冊中心搭建起來。
Eureka架構
基礎架構
上圖簡單的描述了Eureka的基本結構,由3個角色組成:
- Eureka Server: 維護服務信息,包括實例信息,提供服務治理基礎功能的功能,如服務註冊和服務發現。
- Service Provider: 服務提供者,將自身提供的服務註冊到Eureka Server,使服務消費者能夠從Eureka Server中獲取到。
- Service Consumer: 服務消費者,從Eureka Server中獲取註冊服務列表,從而調用相關的服務。
上述三個角色都是抽象的邏輯角色,在實際運行中,這幾個角色可以是同一個實例。
高可用架構
上圖更進一步的展示了3個角色之間的交互。
- 服務提供者向Eureka Server發送服務註冊、服務續約、服務下線等操作。
- Eureka Server 之間會進行註冊服務的同步, 從而保證服務狀態一致。
- 服務消費者向Eureka Server獲取註冊服務列表,並調用服務。
源碼分析
服務註冊
Eureka Server會維護一個服務清單列表,這個列表是一個雙層結構的Map對象,其中第一層的key是服務名,第二層的key是服務實例名。
我們從Eureka Server 的配置類org.springframework.cloud.netflix.eureka.server.EurekaServerAutoConfiguration中可以找到, 發佈的服務實例註冊表是org.springframework.cloud.netflix.eureka.server.InstanceRegistry。
InstanceRegistry類繼承了PeerAwareInstanceRegistryImpl類,所以它支持集羣。
還有一個InstanceRegistry接口,它的完成路徑是com.netflix.eureka.registry.InstanceRegistry。org.springframework.cloud.netflix.eureka.server.InstanceRegistry類是它的實現類。該接口從字面意思可以理解爲實例註冊表,它繼承了LeaseManager接口和LookupService接口。
- LookupService接口主要是查找正常運行的服務實例。
- LeaseManager接口主要是維護可用服務清單的,它將服務的可能期限抽象爲租約期限,該接口負責爲一個實例的租約的創建、續約、和下線。
發佈事件
org.springframework.cloud.netflix.eureka.server.InstanceRegistry類會幫服務註冊、服務續約、服務下線操作發佈一個相應的事件,然後調用父類的方法。
...
public class InstanceRegistry extends PeerAwareInstanceRegistryImpl
implements ApplicationContextAware {
...
//服務註冊
@Override
public void register(final InstanceInfo info, final boolean isReplication) {
handleRegistration(info, resolveInstanceLeaseDuration(info), isReplication);
super.register(info, isReplication);
}
//服務下線
@Override
public boolean cancel(String appName, String serverId, boolean isReplication) {
handleCancelation(appName, serverId, isReplication);
return super.cancel(appName, serverId, isReplication);
}
//服務續約
@Override
public boolean renew(final String appName, final String serverId,
boolean isReplication) {
log("renew " + appName + " serverId " + serverId + ", isReplication {}"
+ isReplication);
List<Application> applications = getSortedApplications();
for (Application input : applications) {
if (input.getName().equals(appName)) {
InstanceInfo instance = null;
for (InstanceInfo info : input.getInstances()) {
if (info.getId().equals(serverId)) {
instance = info;
break;
}
}
publishEvent(new EurekaInstanceRenewedEvent(this, appName, serverId,
instance, isReplication));
break;
}
}
return super.renew(appName, serverId, isReplication);
}
...
}
操作 | 事件 |
---|---|
服務註冊 | EurekaInstanceRegisteredEvent |
服務續約 | EurekaInstanceRenewedEvent |
服務下線 | EurekaInstanceCanceledEvent |
Eureka Server 集羣同步
InstanceRegistry類繼承了 PeerAwareInstanceRegistryImpl類,所以服務註冊、續約、下線等操作完成後,會去調用PeerAwareInstanceRegistryImpl的相關邏輯。而PeerAwareInstanceRegistryImpl中主要是添加了一個廣播的功能,擁有了將服務實例的註冊、續約、下線等操作同步到其它Eureka Server的能力。
com.netflix.eureka.registry.PeerAwareInstanceRegistryImpl
package com.netflix.eureka.registry;
...
public class PeerAwareInstanceRegistryImpl extends AbstractInstanceRegistry implements PeerAwareInstanceRegistry {
...
@Override
public boolean cancel(final String appName, final String id,
final boolean isReplication) {
//調用父類方法
if (super.cancel(appName, id, isReplication)) {
//發送廣播
replicateToPeers(Action.Cancel, appName, id, null, null, isReplication);
...
return true;
}
return false;
}
...
@Override
public void register(final InstanceInfo info, final boolean isReplication) {
int leaseDuration = Lease.DEFAULT_DURATION_IN_SECS;
if (info.getLeaseInfo() != null && info.getLeaseInfo().getDurationInSecs() > 0) {
leaseDuration = info.getLeaseInfo().getDurationInSecs();
}
//調用父類方法
super.register(info, leaseDuration, isReplication);
//發送廣播
replicateToPeers(Action.Register, info.getAppName(), info.getId(), info, null, isReplication);
}
...
public boolean renew(final String appName, final String id, final boolean isReplication) {
//調用父類方法
if (super.renew(appName, id, isReplication)) {
//發送廣播
replicateToPeers(Action.Heartbeat, appName, id, null, null, isReplication);
return true;
}
return false;
}
...
//發送同步消息的邏輯,如果是其它Eureka Server同步過來的,就返回不再發送同步消息。
private void replicateToPeers(Action action, String appName, String id,
InstanceInfo info /* optional */,
InstanceStatus newStatus /* optional */, boolean isReplication) {
Stopwatch tracer = action.getTimer().start();
try {
if (isReplication) {
numberOfReplicationsLastMin.increment();
}
// If it is a replication already, do not replicate again as this will create a poison replication
if (peerEurekaNodes == Collections.EMPTY_LIST || isReplication) {
return;
}
for (final PeerEurekaNode node : peerEurekaNodes.getPeerEurekaNodes()) {
// If the url represents this host, do not replicate to yourself.
if (peerEurekaNodes.isThisMyUrl(node.getServiceUrl())) {
continue;
}
replicateInstanceActionsToPeers(action, appName, id, info, newStatus, node);
}
} finally {
tracer.stop();
}
}
//發送同步消息的操作, PeerEurekaNode.replicationClient最終調用的是JerseyReplicationClient
private void replicateInstanceActionsToPeers(Action action, String appName,
String id, InstanceInfo info, InstanceStatus newStatus,
PeerEurekaNode node) {
try {
InstanceInfo infoFromRegistry = null;
CurrentRequestVersion.set(Version.V2);
switch (action) {
case Cancel:
node.cancel(appName, id);
break;
case Heartbeat:
InstanceStatus overriddenStatus = overriddenInstanceStatusMap.get(id);
infoFromRegistry = getInstanceByAppAndId(appName, id, false);
node.heartbeat(appName, id, infoFromRegistry, overriddenStatus, false);
break;
case Register:
node.register(info);
break;
case StatusUpdate:
infoFromRegistry = getInstanceByAppAndId(appName, id, false);
node.statusUpdate(appName, id, newStatus, infoFromRegistry);
break;
case DeleteStatusOverride:
infoFromRegistry = getInstanceByAppAndId(appName, id, false);
node.deleteStatusOverride(appName, id, infoFromRegistry);
break;
}
} catch (Throwable t) {
logger.error("Cannot replicate information to {} for action {}", node.getServiceUrl(), action.name(), t);
}
}
...
}
在同步過程中,會通過PeerEurekaNode對象中的replicationClient字段發送消息,該字段是com.netflix.eureka.cluster.HttpReplicationClient接口的實現類,默認是com.netflix.eureka.transport.JerseyReplicationClient。
從上圖中可以看到,該類是AbstractJerseyEurekaHttpClient類的子類,在上一章中我們分析過AbstractJerseyEurekaHttpClient類,這裏就只分析JerseyReplicationClient類了。
JerseyReplicationClient類除了對服務續約、服務狀態變更進行了擴展外,就是在http請求頭中設置x-netflix-discovery-replication=true。
com.netflix.eureka.transport.JerseyReplicationClient
public class JerseyReplicationClient extends AbstractJerseyEurekaHttpClient implements HttpReplicationClient {
...
@Override
protected void addExtraHeaders(Builder webResource) {
webResource.header(PeerEurekaNode.HEADER_REPLICATION, "true");
}
...
}
com.netflix.eureka.cluster.PeerEurekaNode
public class PeerEurekaNode {
...
public static final String HEADER_REPLICATION = "x-netflix-discovery-replication";
...
}
Eureka Server之間的同步是在具體操作之後。
服務註冊
比如服務註冊,是在服務註冊完成後,再執行服務同步。所以PeerAwareInstanceRegistryImpl會在同步之前調用父類AbstractInstanceRegistry的相關邏輯,比如服務註冊,而我們在InstanceRegistry類和PeerAwareInstanceRegistryImpl類中都沒有看到服務註冊的邏輯,那麼主要邏輯就肯定在它們的父類com.netflix.eureka.registry.AbstractInstanceRegistry類中了。
package com.netflix.eureka.registry;
...
public abstract class AbstractInstanceRegistry implements InstanceRegistry {
...
//服務清單
private final ConcurrentHashMap<String, Map<String, Lease<InstanceInfo>>> registry
= new ConcurrentHashMap<String, Map<String, Lease<InstanceInfo>>>();
...
public void register(InstanceInfo registrant, int leaseDuration, boolean isReplication) {
try {
read.lock();
//獲取服務的所有實例
Map<String, Lease<InstanceInfo>> gMap = registry.get(registrant.getAppName());
REGISTER.increment(isReplication);
if (gMap == null) {
final ConcurrentHashMap<String, Lease<InstanceInfo>> gNewMap = new ConcurrentHashMap<String, Lease<InstanceInfo>>();
gMap = registry.putIfAbsent(registrant.getAppName(), gNewMap);
if (gMap == null) {
gMap = gNewMap;
}
}
//獲取實例的信息,如果以前沒有註冊過,就返回null
Lease<InstanceInfo> 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();
Long registrationLastDirtyTimestamp = registrant.getLastDirtyTimestamp();
logger.debug("Existing lease found (existing={}, provided={}", existingLastDirtyTimestamp, registrationLastDirtyTimestamp);
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
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<InstanceInfo> lease = new Lease<InstanceInfo>(registrant, leaseDuration);
if (existingLease != null) {
//如果以前註冊過,就設置爲以前的註冊(啓用)時間。(沒有明白這個操作的目的)
lease.setServiceUpTimestamp(existingLease.getServiceUpTimestamp());
}
//保存進到服務實例集合中
gMap.put(registrant.getId(), lease);
synchronized (recentRegisteredQueue) {
recentRegisteredQueue.add(new Pair<Long, String>(
System.currentTimeMillis(),
registrant.getAppName() + "(" + registrant.getId() + ")"));
}
// 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();
}
}
...
}
上面就是Eureka Server服務註冊的主要邏輯。
服務下線
在瞭解了服務註冊的邏輯後, 服務下線的邏輯就簡單很多了。主要是將服務實例從服務實例集合中刪除,並留下刪除時間和記錄。
package com.netflix.eureka.registry;
...
public abstract class AbstractInstanceRegistry implements InstanceRegistry {
...
@Override
public boolean cancel(String appName, String id, boolean isReplication) {
return internalCancel(appName, id, isReplication);
}
/**
* {@link #cancel(String, String, boolean)} method is overridden by {@link PeerAwareInstanceRegistry}, so each
* cancel request is replicated to the peers. This is however not desired for expires which would be counted
* in the remote peers as valid cancellations, so self preservation mode would not kick-in.
*/
protected boolean internalCancel(String appName, String id, boolean isReplication) {
try {
read.lock();
//統計下線服務實例數
CANCEL.increment(isReplication);
Map<String, Lease<InstanceInfo>> gMap = registry.get(appName);
Lease<InstanceInfo> leaseToCancel = null;
if (gMap != null) {
//刪除下線的服務實例
leaseToCancel = gMap.remove(id);
}
synchronized (recentCanceledQueue) {
recentCanceledQueue.add(new Pair<Long, String>(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 Server報告, 我還正常, 這樣纔不會被服務剔除機制給刪除掉。在代碼邏輯裏面,就是更新一個租約的最後修改時間,而這個最後修改時間一般會往後加一個租憑期限,這個期限默認是90秒;而在判斷租約過期時,也會再加一次租憑期限,所以,默認情況下,一個服務實例如果180秒還沒有續約的話,就會判定這個服務實例已不能正常提供服務。
package com.netflix.eureka.registry;
...
public abstract class AbstractInstanceRegistry implements InstanceRegistry {
...
public void register(InstanceInfo registrant, int leaseDuration, boolean isReplication) {
...
//將租憑期限做爲租約的實例屬性,這個值是從子類PeerAwareInstanceRegistryImpl傳遞過來的
Lease<InstanceInfo> lease = new Lease<InstanceInfo>(registrant, leaseDuration);
...
}
...
//服務續約的邏輯
public boolean renew(String appName, String id, boolean isReplication) {
RENEW.increment(isReplication);
Map<String, Lease<InstanceInfo>> gMap = registry.get(appName);
Lease<InstanceInfo> leaseToRenew = null;
if (gMap != null) {
leaseToRenew = gMap.get(id);
}
if (leaseToRenew == null) {
RENEW_NOT_FOUND.increment(isReplication);
logger.warn("DS: Registry: lease doesn't exist, registering resource: {} - {}", appName, id);
return false;
} else {
InstanceInfo instanceInfo = leaseToRenew.getHolder();
if (instanceInfo != null) {
// touchASGCache(instanceInfo.getASGName());
InstanceStatus overriddenInstanceStatus = this.getOverriddenInstanceStatus(
instanceInfo, leaseToRenew, isReplication);
if (overriddenInstanceStatus == InstanceStatus.UNKNOWN) {
logger.info("Instance status UNKNOWN possibly due to deleted override for instance {}"
+ "; re-register required", instanceInfo.getId());
RENEW_NOT_FOUND.increment(isReplication);
return false;
}
if (!instanceInfo.getStatus().equals(overriddenInstanceStatus)) {
Object[] args = {
instanceInfo.getStatus().name(),
instanceInfo.getOverriddenStatus().name(),
instanceInfo.getId()
};
logger.info(
"The instance status {} is different from overridden instance status {} for instance {}. "
+ "Hence setting the status to overridden status", args);
instanceInfo.setStatus(overriddenInstanceStatus);
}
}
renewsLastMin.increment();
//更新續約時間/最後修改時間
leaseToRenew.renew();
return true;
}
}
...
}
public class Lease<T> {
...
//默認的租憑期限
public static final int DEFAULT_DURATION_IN_SECS = 90;
...
private long duration;
public Lease(T r, int durationInSecs) {
holder = r;
registrationTimestamp = System.currentTimeMillis();
lastUpdateTimestamp = registrationTimestamp;
duration = (durationInSecs * 1000);
}
//續約更新最後修改時間
public void renew() {
lastUpdateTimestamp = System.currentTimeMillis() + duration;
}
...
}
public class PeerAwareInstanceRegistryImpl extends AbstractInstanceRegistry implements PeerAwareInstanceRegistry {
...
@Override
public void register(final InstanceInfo info, final boolean isReplication) {
//設置默認租憑期限
int leaseDuration = Lease.DEFAULT_DURATION_IN_SECS;
if (info.getLeaseInfo() != null && info.getLeaseInfo().getDurationInSecs() > 0) {
//如果服務實例有設置租憑期限,就設置爲服務實例的租憑期限。
leaseDuration = info.getLeaseInfo().getDurationInSecs();
}
super.register(info, leaseDuration, isReplication);
replicateToPeers(Action.Register, info.getAppName(), info.getId(), info, null, isReplication);
}
...
}
服務剔除
上面分析中說到如果一個服務實例180秒還沒有更新最後修改時間的話, 就將會被服務剔除機制刪除。
在該機制中,還有一個保護機制,就是如果在一定時間段內,判斷服務續約成功的實例數低於續約閾值(最大心跳總數的85%,相當於服務實例數*2,因爲心跳消息默認30s發送一次),Eureka Server會將當前的實例信息保護起來,讓這些實例不會過期。當然前提是要開啓自我保護機制,默認是開啓的,也可以配置eureka.server.enable-self-preservation=false來關閉保護機制。
public abstract class AbstractInstanceRegistry implements InstanceRegistry {
...
@Override
public void evict() {
evict(0l);
}
//服務剔除邏輯
public void evict(long additionalLeaseMs) {
logger.debug("Running the evict task");
//保護機制,實現邏輯在PeerAwareInstanceRegistryImpl中。
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<Lease<InstanceInfo>> expiredLeases = new ArrayList<>();
for (Entry<String, Map<String, Lease<InstanceInfo>>> groupEntry : registry.entrySet()) {
Map<String, Lease<InstanceInfo>> leaseMap = groupEntry.getValue();
if (leaseMap != null) {
for (Entry<String, Lease<InstanceInfo>> leaseEntry : leaseMap.entrySet()) {
Lease<InstanceInfo> 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);
//傳入當前時間爲種子生成隨機,避免 Java 的僞隨機情況
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);
//隨機調換後面的元素到當前位置( i )
Collections.swap(expiredLeases, i, next);
Lease<InstanceInfo> 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);
}
}
}
...
}
public class Lease<T> {
...
//默認的租憑期限
public static final int DEFAULT_DURATION_IN_SECS = 90;
...
private long duration;
public Lease(T r, int durationInSecs) {
holder = r;
registrationTimestamp = System.currentTimeMillis();
lastUpdateTimestamp = registrationTimestamp;
duration = (durationInSecs * 1000);
}
//續約更新最後修改時間
public void renew() {
lastUpdateTimestamp = System.currentTimeMillis() + duration;
}
//判斷是否過期,當前時間>最後修改時間+租憑期限
public boolean isExpired(long additionalLeaseMs) {
return (evictionTimestamp > 0 || System.currentTimeMillis() > (lastUpdateTimestamp + duration + additionalLeaseMs));
}
...
}
下面我們來看看自保護機制的邏輯
public class PeerAwareInstanceRegistryImpl extends AbstractInstanceRegistry implements PeerAwareInstanceRegistry {
...
//定時更新續約閾值,默認15分鐘
private void scheduleRenewalThresholdUpdateTask() {
timer.schedule(new TimerTask() {
@Override
public void run() {
updateRenewalThreshold();
}
}, serverConfig.getRenewalThresholdUpdateIntervalMs(),
serverConfig.getRenewalThresholdUpdateIntervalMs());
}
...
//保護機制的判斷邏輯
@Override
public boolean isLeaseExpirationEnabled() {
if (!isSelfPreservationModeEnabled()) {
// The self preservation mode is disabled, hence allowing the instances to expire.
return true;
}
//判斷服務續約成功的實例數低於註冊數的85%
return numberOfRenewsPerMinThreshold > 0 && getNumOfRenewsInLastMin() > numberOfRenewsPerMinThreshold;
}
//判斷是否打開保護機制
@Override
public boolean isSelfPreservationModeEnabled() {
return serverConfig.shouldEnableSelfPreservation();
}
...
//更新續約閾值
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;
}
}
}
synchronized (lock) {
// Update threshold only if the threshold is greater than the
// current expected threshold of if the self preservation is disabled.
// 更新續約閾值的判斷(這裏存在一定問題,會使保護機制一直存在),應用實例每分鐘最大心跳數( count * 2 ) 小於期望最小每分鐘續租次數( serverConfig.getRenewalPercentThreshold() * numberOfRenewsPerMinThreshold ),不重新計算
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);
}
}
...
}
上面的代碼中有兩個邏輯
自我保護的判斷邏輯
isLeaseExpirationEnabled()方法是其入口,首先判斷是否打開保護機制,默認開啓。然後再判斷服務續約成功的實例數低於續約閾值,默認是註冊數*2的85%,因爲心跳是30秒發送一次,而剔除機制是1分鐘執行一次。更新自我保護
scheduleRenewalThresholdUpdateTask()方法是該邏輯的入口, 在這個方法中,創建了一個定時任務,默認是每15分鐘更新一次續約閾值。
updateRenewalThreshold()方法封裝了更新閾值的邏輯。但是如果應用實例每分鐘最大心跳數( count * 2 ) 小於期望最小每分鐘續租次數( serverConfig.getRenewalPercentThreshold() * numberOfRenewsPerMinThreshold ),不重新計算時,並且服務實例確實不可用了,那麼自我保護狀態會一直存在。
參考資料
《spring cloud 微服務實戰》
Eureka 源碼解析 —— 應用實例註冊發現(四)之自我保護機制
深度剖析服務發現組件Netflix Eureka