前言
上一篇介绍了Eureka Server端服务的注册功能,本篇将继续介绍Eureka的下线、续约相关的源码和优化点。
Server端服务的续约
当客户端通过心跳检测来续约时,就会触发Eureka Server端去执行服务续约操作。
实例的下线和续约的实现类,主要都在实例策略类InstanceRegistry中,Controller:
public class InstanceRegistry extends PeerAwareInstanceRegistryImpl {
@Override
public boolean renew(final String appName, final String serverId, boolean isReplication) {
// 所有已经注册的服务列表
List<Application> applications = getSortedApplications();
// 遍历
for (Application input : applications) {
// 先匹配服务名
// 这里很好理解, 一个服务可能部署了多个节点, 但appName是一样的
// 区别在于instanceId, 也就是IP+Port是不同的
if (input.getName().equals(appName)) {
InstanceInfo instance = null;
// 再匹配instanceId, 并存到instance变量中
for (InstanceInfo info : input.getInstances()) {
if (info.getId().equals(serverId)) {
instance = info;
break;
}
}
// 触发一个事件, instance就是上面if里设置的
publishEvent(new EurekaInstanceRenewedEvent(this, appName, serverId,
instance, isReplication));
break;
}
}
// 执行续约的逻辑, isReplication这里传入的值为false
return super.renew(appName, serverId, isReplication);
}
}
接着看最后一行代码,是续约的主要代码:
# PeerAwareInstanceRegistryImpl
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;
}
其中处理的逻辑在if的 super.renew(..) 方法中,继续跟进:
public abstract class AbstractInstanceRegistry implements InstanceRegistry {
public boolean renew(String appName, String id, boolean isReplication) {
RENEW.increment(isReplication);
// 从registry注册表中获取appName所有注册了的服务
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);
return false;
} else {
// 获取续约的实例相关信息, 并进行状态的检查
InstanceInfo instanceInfo = leaseToRenew.getHolder();
if (instanceInfo != null) {
InstanceStatus overriddenInstanceStatus = this.getOverriddenInstanceStatus(
instanceInfo, leaseToRenew, isReplication);
if (overriddenInstanceStatus == InstanceStatus.UNKNOWN) {
RENEW_NOT_FOUND.increment(isReplication);
return false;
}
if (!instanceInfo.getStatus().equals(overriddenInstanceStatus)) {
instanceInfo.setStatusWithoutDirty(overriddenInstanceStatus);
}
}
renewsLastMin.increment();
// 续约, 仅仅将租约的最后更新时间 "lastUpdateTimestamp" 属性的值更新为当前时间
leaseToRenew.renew();
return true;
}
}
}
// 更新续租时间为当前日期
public class Lease<T> {
// 其他代码, 省略
public void renew() {
lastUpdateTimestamp = System.currentTimeMillis() + duration;
}
}
在上面更新租约的信息中,if代码块里的replicateToPeers(..)方法,在第二篇已经说过,这里直接略过了。
简单总结一下上面的代码:
- isReplication=false,所以会续约信息会一直同步到其他的所有peer
- 续约主要操作,其实就是将租约的最后更新时间更新为当前时间(当然事件通知不是主要的)
Server端服务的下线
当客户端需要停止服务时,可以手动下线,即通过调用Rest请求,直接要求Eureka Server对该服务执行下线操作。
主要逻辑如下:
public class InstanceRegistry extends PeerAwareInstanceRegistryImpl
implements ApplicationContextAware {
// client主动下线
@Override
public boolean cancel(String appName, String serverId, boolean isReplication) {
// 触发一个事件, 不细说
handleCancelation(appName, serverId, isReplication);
return super.cancel(appName, serverId, isReplication);
}
}
public abstract class AbstractInstanceRegistry implements InstanceRegistry {
// client请求服务下线, 这里的isReplication传入的也是false
@Override
public boolean cancel(String appName, String id, boolean isReplication) {
return internalCancel(appName, id, isReplication);
}
protected boolean internalCancel(String appName, String id, boolean isReplication) {
try {
read.lock();
CANCEL.increment(isReplication);
// 根据appName, 获取所有的服务列表
Map<String, Lease<InstanceInfo>> gMap = registry.get(appName);
// 封装主动下线的服务包装类
Lease<InstanceInfo> leaseToCancel = null;
if (gMap != null) {
// 把要下线的服务从gMap中移除掉, 并返回给leaseToCancel变量
leaseToCancel = gMap.remove(id);
}
// 添加到最近下线队列中
recentCanceledQueue.add(new Pair<Long, String>(System.currentTimeMillis(), appName + "(" + id + ")"));
InstanceStatus instanceStatus = overriddenInstanceStatusMap.remove(id);
if (instanceStatus != null) {
logger.debug("Removed instance id ...");
}
if (leaseToCancel == null) {
CANCEL_NOT_FOUND.increment(isReplication);
return false;
} else {
// 下线逻辑
leaseToCancel.cancel();
// 获取租约的具体服务信息
InstanceInfo instanceInfo = leaseToCancel.getHolder();
String vip = null;
String svip = null;
if (instanceInfo != null) {
// 触发事件类型为DELETED, 即下线, 见下面的代码, 仅仅更新了时间属性值
instanceInfo.setActionType(ActionType.DELETED);
// 因为上面移除了id这个项对应的服务, 所以自然要加到最近修改的队列中
recentlyChangedQueue.add(new RecentlyChangedItem(leaseToCancel));
// 更新下线的这个服务的最后更新时间属性值
instanceInfo.setLastUpdatedTimestamp();
vip = instanceInfo.getVIPAddress();
svip = instanceInfo.getSecureVipAddress();
}
// 清空缓存, 前面文章有遇到过, 后面会详细说
invalidateCache(appName, vip, svip);
}
} finally {
read.unlock();
}
synchronized (lock) {
// 由于有服务下线, 所以得更新心跳数量和自我保护的阈值
if (this.expectedNumberOfClientsSendingRenews > 0) {
this.expectedNumberOfClientsSendingRenews = this.expectedNumberOfClientsSendingRenews - 1;
updateRenewsPerMinThreshold();
}
}
return true;
}
}
public class Lease<T> {
// 更新 剔除/下线时间为当前时间
public void cancel() {
if (evictionTimestamp <= 0) {
evictionTimestamp = System.currentTimeMillis();
}
}
}
总结一下剔除处理的主要逻辑
- 从注册表registry(我有时也称作一级缓存)中移除
- 清除缓存中这个下线的服务
细节
在线上环境,有可能需要对某个微服务执行下线操作,正确的做法,应该是先停止服务,再通过rest等命令从注册中心下线,原因在于,如果先下线,再停服,由于客户端的心跳功能,可能又会续约上了,从而导致出现问题。
再谈一下isReplication
本篇介绍了Server端服务的续约和下线的处理逻辑,包括上一篇的服务注册,都涉及到了isReplication这个字段,前面提到过:
- 当isReplication为false时:表示服务的注册请求,会一直进行集群间的同步
- 当isReplication为true时:表示集群间的服务同步请求
通过前面源码处,该参数的值,可以得出总结
-
服务注册: 在ApplicationResource.addInstance(..)中传入的是null,所以在后面的传递过程中,isReplication=false, 所以在PeerAwareInstanceRegistryImpl.replicateToPeers()中会执行集群同步。 但不会一直同步,因为在下一下时候,该参数就 被修改成了true,所以不会一直传递。概括起来: 会注册一次、同步仅一次,结束。
-
续约/主动下线: 第一次进入的时候,InstanceRegistry.renew()传入的值为false,所以在第一次进入PeerAwareInstanceRegistryImpl.replicateToPeers()方法的时候,是可以进行集群同步方法内的, 在PeerAwareInstanceRegistryImpl.replicateInstanceActionsToPeers()的Heartbeat这个case中,node.heartbeat()方法里,创建InstanceReplicationTask实例的时候,在构造函数中传入了isReplication=false,所以下次还会一直同步。主动下线也是同样的逻辑。