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;
}