1. 環境搭建
代碼已經上傳至 https://github.com/masteryourself-tutorial/tutorial-spring,工程是
tutorial-spring-cloud/tutorial-spring-cloud-eureka/tutorial-spring-cloud-eureka-single-7001
2. 源碼解析
詳細的源碼註釋可參考 https://github.com/masteryourself/eureka
2.1 服務 註冊/續約/下線 處理
2.1.1 流程分析
2.1.2 核心源碼剖析
1. com.netflix.eureka.registry.AbstractInstanceRegistry#register
public void register(InstanceInfo registrant, int leaseDuration, boolean isReplication) {
try {
read.lock();
// 註冊中心,實際上就是一個 ConcurrentHashMap
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;
}
}
// 正常來說,第一次註冊時候 existingLease 是空數據,走 else 流程
// 但是也存在這種情況:第一次註冊時候,由於網絡原因還沒有處理,心跳的請求已經先處理結束,然後這裏的 existingLease 是有值的
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);
// 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.
// 存在這種情況:心跳的請求先處理了,然後再處理註冊,這時候就有可能 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.expectedNumberOfClientsSendingRenews > 0) {
// Since the client wants to register it, increase the number of clients sending renews
this.expectedNumberOfClientsSendingRenews = this.expectedNumberOfClientsSendingRenews + 1;
// 計算自我保護機制的相關數據閾值
updateRenewsPerMinThreshold();
}
}
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() + ")"));
}
...
} finally {
read.unlock();
}
}
2. com.netflix.eureka.registry.PeerAwareInstanceRegistryImpl#replicateInstanceActionsToPeers
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);
}
}
3. com.netflix.eureka.cluster.PeerEurekaNode#register
public void register(final InstanceInfo info) throws Exception {
// 獲取過期時間
long expiryTime = System.currentTimeMillis() + getLeaseRenewalOf(info);
batchingDispatcher.process(
taskId("register", info),
new InstanceReplicationTask(targetHost, Action.Register, info, null, true) {
public EurekaHttpResponse<Void> execute() {
// 在過期時間之前需要完成 register() 任務
return replicationClient.register(info);
}
},
expiryTime
);
}
4. com.netflix.eureka.registry.AbstractInstanceRegistry#renew
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) {
// 獲取其對應的 Lease 信息
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) {
...
if (!instanceInfo.getStatus().equals(overriddenInstanceStatus)) {
logger.info(
"The instance status {} is different from overridden instance status {} for instance {}. "
+ "Hence setting the status to overridden status", instanceInfo.getStatus().name(),
instanceInfo.getOverriddenStatus().name(),
instanceInfo.getId());
// 設置狀態信息
instanceInfo.setStatusWithoutDirty(overriddenInstanceStatus);
}
}
// 計數器 +1
renewsLastMin.increment();
// 更新最後修改時間
leaseToRenew.renew();
return true;
}
}
5. com.netflix.eureka.cluster.PeerEurekaNode#heartbeat
public void heartbeat(final String appName, final String id,
final InstanceInfo info, final InstanceStatus overriddenStatus,
boolean primeConnection) throws Throwable {
// 判斷是否是第一次
if (primeConnection) {
// We do not care about the result for priming request.
replicationClient.sendHeartBeat(appName, id, info, overriddenStatus);
return;
}
ReplicationTask replicationTask = new InstanceReplicationTask(targetHost, Action.Heartbeat, info, overriddenStatus, false) {
@Override
public EurekaHttpResponse<InstanceInfo> execute() throws Throwable {
return replicationClient.sendHeartBeat(appName, id, info, overriddenStatus);
}
...
};
long expiryTime = System.currentTimeMillis() + getLeaseRenewalOf(info);
// 在過期時間之前需要完成 sendHeartBeat() 任務
batchingDispatcher.process(taskId("heartbeat", info), replicationTask, expiryTime);
}
6. com.netflix.eureka.registry.AbstractInstanceRegistry#internalCancel
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 + ")"));
}
// 從狀態 map 中刪除
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();
...
return true;
}
} finally {
read.unlock();
}
}
7. com.netflix.eureka.cluster.PeerEurekaNode#cancel
public void cancel(final String appName, final String id) throws Exception {
long expiryTime = System.currentTimeMillis() + maxProcessingDelayMs;
batchingDispatcher.process(
taskId("cancel", appName, id),
new InstanceReplicationTask(targetHost, Action.Cancel, appName, id) {
@Override
public EurekaHttpResponse<Void> execute() {
// 在過期時間之前需要完成 cancel() 任務
return replicationClient.cancel(appName, id);
}
...
},
expiryTime
);
}
2.2 EurekaServerAutoConfiguration 裝配流程
spring-cloud-starter-netflix-eureka-server-xxx.jar
-> spring-cloud-netflix-eureka-server-xxx.jar
-> 定義 spring.factories
文件 -> 向容器中導入 EurekaServerAutoConfiguration
組件,其中配置文件內容如下:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.netflix.eureka.server.EurekaServerAutoConfiguration
EurekaServerAutoConfiguration
-> EurekaServerInitializerConfiguration
+ EurekaServerConfigBean
EurekaServerConfigBean
其實就是配置類,自動關聯 eureka.server
前綴的配置文件
EurekaServerInitializerConfiguration
實現了 SmartLifecycle
接口,Spring 在 finishRefresh()
-> getLifecycleProcessor().onRefresh()
方法中會去調用 start()
方法
2.3 定時清理 client 信息
2.3.1 流程分析
2.3.2 核心源碼剖析
1. com.netflix.eureka.registry.AbstractInstanceRegistry#postInit
protected void postInit() {
// 啓動 renewsLastMin 定時器,即更新 Renews(last min)
renewsLastMin.start();
if (evictionTaskRef.get() != null) {
evictionTaskRef.get().cancel();
}
evictionTaskRef.set(new EvictionTask());
// 啓動 evictionTimer 定時器,清理註冊中心信息
evictionTimer.schedule(evictionTaskRef.get(),
serverConfig.getEvictionIntervalTimerInMs(),
serverConfig.getEvictionIntervalTimerInMs());
}
2. com.netflix.eureka.registry.AbstractInstanceRegistry.EvictionTask#getCompensationTimeMs
long getCompensationTimeMs() {
// 當前時間
long currNanos = getCurrentTimeNano();
// 上一次清理後的時間
long lastNanos = lastExecutionNanosRef.getAndSet(currNanos);
if (lastNanos == 0l) {
return 0l;
}
// 算出上一次的實際清理時間
long elapsedMs = TimeUnit.NANOSECONDS.toMillis(currNanos - lastNanos);
// 如果上次實際清理時間 > 指定的清理時間,計算出補償時間
long compensationTime = elapsedMs - serverConfig.getEvictionIntervalTimerInMs();
return compensationTime <= 0l ? 0l : compensationTime;
}
3. com.netflix.eureka.registry.AbstractInstanceRegistry#evict(long)
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<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();
// 判斷依據:是否過期,當前 holder 是否爲空
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);
// 洗牌清除客戶端數量,避免每次清除的都是前面的 client 信息
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<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);
// 清理工作,即之前分析的 cancel() 服務下線操作
internalCancel(appName, id, false);
}
}
}
4. com.netflix.eureka.registry.PeerAwareInstanceRegistryImpl#isLeaseExpirationEnabled
public boolean isLeaseExpirationEnabled() {
// 如果禁用了自我保護機制,直接返回
if (!isSelfPreservationModeEnabled()) {
// The self preservation mode is disabled, hence allowing the instances to expire.
return true;
}
// 當 renewsLastMin (實際在最後一分鐘收到的 client 端發送的續約的數量) < numberOfRenewsPerMinThreshold(期待每分鐘收到 client 端發送的續約總數) 時,觸發自動保護機制
return numberOfRenewsPerMinThreshold > 0 && getNumOfRenewsInLastMin() > numberOfRenewsPerMinThreshold;
}