小白也能读懂springcloud系列之Eureka Server 源码解析

本文详细解析了Eureka服务注册与续约机制,包括处理服务注册、续约、下架的过程,以及如何通过Jersey框架处理请求,实现客户端与服务端的交互。深入探讨了服务端如何验证、注册和更新客户端信息,以及心跳机制和自我保护策略。

一、源码解析内容

1. 处理服务注册
2. 处理服务续约
3. 处理服务下架
4. 定时清除失联的Client

二、处理服务注册

主要任务:
1. 将客户端注册请求中携带的InstanceInfo写入到服务端注册表
2. 将服务端注册表中的InstanceInfo同步到其他server

(注意这里面说的服务端注册表的结构是一个双层Map,外层Map的key为服务名称,value为一个内层Map,内层Map的key为instanceId,value为一个Lease(续约)对象)

通过上一篇我们阅读了Client源码后发现,Eureka Client与Eureka Server之间实际上是通过一个叫做Jersey框架来进行请求处理的,客户端通过post请求将要注册的InstanceInfo信息发送给服务端来完成注册,所以我们跟服务端源码的切入点就是服务端接收请求的处理器(在SpringMVC中叫做Controller,而在Jersey中叫做Resource)。故我们首先找到ApplicationResource

/**
 * 指明这是一个Jersey处理器,用于处理特定应用的请求
 * A <em>jersey</em> resource that handles request related to a particular
 */
@Produces({"application/xml", "application/json"})
public class ApplicationResource {
    private static final Logger logger = LoggerFactory.getLogger(ApplicationResource.class);

    private final String appName;
    private final EurekaServerConfig serverConfig;
    private final PeerAwareInstanceRegistry registry;
    private final ResponseCache responseCache;

在这个类中我们需要找到一个方法叫做addInstance(),在这个方法中可以看到用于处理post请求且传入参数是InstanceInfo对象,从方法名可以看出是新增Instance,也就是注册,下面我将这个方法分为几个部分进行讲解:
在这里插入图片描述
第一部分
这一块代码的作用是对传过来的InstanceInfo进行数据校验,我们不用关注

logger.debug("Registering instance {} (replication={})", info.getId(), isReplication);
		// 这一块代码的作用是对传过来的InstanceInfo进行数据校验,我们不用关注
        // validate that the instanceinfo contains all the necessary required fields
        if (isBlank(info.getId())) {
            return Response.status(400).entity("Missing instanceId").build();
        } else if (isBlank(info.getHostName())) {
            return Response.status(400).entity("Missing hostname").build();
        } else if (isBlank(info.getIPAddr())) {
            return Response.status(400).entity("Missing ip address").build();
        } else if (isBlank(info.getAppName())) {
            return Response.status(400).entity("Missing appName").build();
        } else if (!appName.equals(info.getAppName())) {
            return Response.status(400).entity("Mismatched appName, expecting " + appName + " but was " + info.getAppName()).build();
        } else if (info.getDataCenterInfo() == null) {
            return Response.status(400).entity("Missing dataCenterInfo").build();
        } else if (info.getDataCenterInfo().getName() == null) {
            return Response.status(400).entity("Missing dataCenterInfo Name").build();
        }

第二部分
这一块代码的作用是处理客户端可能向一个缺失数据的数据中心进行注册,这一块我们也不需要关注

// 这一块代码的作用是处理客户端可能向一个缺失数据的数据中心进行注册,这一块我们不关注
// handle cases where clients may be registering with bad DataCenterInfo with missing data
        DataCenterInfo dataCenterInfo = info.getDataCenterInfo();
        if (dataCenterInfo instanceof UniqueIdentifier) {
            String dataCenterInfoId = ((UniqueIdentifier) dataCenterInfo).getId();
            if (isBlank(dataCenterInfoId)) {
                boolean experimental = "true".equalsIgnoreCase(serverConfig.getExperimental("registration.validation.dataCenterInfoId"));
                if (experimental) {
                    String entity = "DataCenterInfo of type " + dataCenterInfo.getClass() + " must contain a valid id";
                    return Response.status(400).entity(entity).build();
                } else if (dataCenterInfo instanceof AmazonInfo) {
                    AmazonInfo amazonInfo = (AmazonInfo) dataCenterInfo;
                    String effectiveId = amazonInfo.get(AmazonInfo.MetaDataKey.instanceId);
                    if (effectiveId == null) {
                        amazonInfo.getMetadata().put(AmazonInfo.MetaDataKey.instanceId.getName(), info.getId());
                    }
                } else {
                    logger.warn("Registering DataCenterInfo of type {} without an appropriate id", dataCenterInfo.getClass());
                }
            }
        }

第三部分
这一块代码的作用是处理注册,这里我们要重点关注

// 处理注册,这里我们要重点关注
registry.register(info, "true".equals(isReplication));
return Response.status(204).build();  // 204 to be backwards compatible

跟进registry.register(info, “true”.equals(isReplication))这个方法,方法参数就是传递进来的InstanceInfo对象,另一个isReplication指的是当前这个注册是客户端向服务端的注册,还是服务端与服务端之间的Replicate
在这里插入图片描述
跟进register()
在这里插入图片描述

@Override
	// 注意这里传进来的isReplication是false
	public void register(final InstanceInfo info, final boolean isReplication) {
		handleRegistration(info, resolveInstanceLeaseDuration(info), isReplication);
		super.register(info, isReplication);
	}

这里面handleRegistration()方法不用关注,其主要是发布事件,我们主要关注register()方法,跟进super.register(info, isReplication)

@Override
    public void register(final InstanceInfo info, final boolean isReplication) {
    	// 这是一个常量默认是90秒
        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);
    }

首先Lease.DEFAULT_DURATION_IN_SECS 是一个常量默认为90秒,其在配置文件中的配置是
在这里插入图片描述

// info.getLeaseInfo()肯定不为空,getDurationInSecs()由于我们设置是3秒肯定大于0
// 所以if条件成立,此时将设置续约时间为设置值
if (info.getLeaseInfo() != null && info.getLeaseInfo().getDurationInSecs() > 0) {
   leaseDuration = info.getLeaseInfo().getDurationInSecs();
}
// 将客户端注册请求中携带的InstanceInfo写入到服务端注册表
super.register(info, leaseDuration, isReplication);
// 将服务端注册表中的InstanceInfo同步到其他server
replicateToPeers(Action.Register, info.getAppName(), info.getId(), info, null, isReplication);

首先跟进super.register(info, leaseDuration, isReplication)
在这里插入图片描述
首先注意这里的方法是注册,为什么会先加读锁?
答:假设现在有线程正在修改注册表,加上读锁后,其他客户端仍然可以进行下载(读取)注册表操作,如果这里加的是写锁,那么读取和修改不能同时进行。换句话说这也是AP的一个体现,在修改注册表的同时,读操作是可以正常执行的。

// 这就是服务端注册表的结构
private final ConcurrentHashMap<String, Map<String, Lease<InstanceInfo>>> registry
            = new ConcurrentHashMap<String, Map<String, Lease<InstanceInfo>>>();
// 根据服务名称获取其下所有提供者
Map<String, Lease<InstanceInfo>> gMap = registry.get(registrant.getAppName())

这里面先要说一下Lease对象
在这里插入图片描述
也就是说我们可以把Lease对象看作是一个主机,只不过Lease中增加了很多跟时间相关的属性

public static final int DEFAULT_DURATION_IN_SECS = 90;
private T holder;
private long evictionTimestamp; // 删除的时间
private long registrationTimestamp; // 注册的时间
private long serviceUpTimestamp; // 服务状态为UP的时间
// Make it volatile so that the expiration task would see this quicker
private volatile long lastUpdateTimestamp; // 最新更新的时间
private long duration;

创建Lease对象,实际上就是对一些属性进行初始化操作

public Lease(T r, int durationInSecs) {
    holder = r;
    registrationTimestamp = System.currentTimeMillis();
    lastUpdateTimestamp = registrationTimestamp;
    duration = (durationInSecs * 1000); // 秒变毫秒
}

继续往下

// 这一行主要是进行计数统计
REGISTER.increment(isReplication);

跟进increment(),这块代码还是很简单的,counter记录的是客户端注册与服务器之间的复制次数的总和,而myZoneCounter记录的仅仅是客户端注册次数
在这里插入图片描述
继续向下

// 由于第一次刚又客户端进行注册,所以gMap肯定为null
if (gMap == null) {
    final ConcurrentHashMap<String, Lease<InstanceInfo>> gNewMap = new ConcurrentHashMap<String, Lease<InstanceInfo>>();
    // putIfAbsent注意这个方法
    // 如果传入key对应的value已经存在,就返回存在的value,不进行替换。
    // 如果不存在,就添加key和value,返回null
    // 这里面的gMap很显然是null
    gMap = registry.putIfAbsent(registrant.getAppName(), gNewMap);
    if (gMap == null) {
        gMap = gNewMap;
    }
}

继续向下

// registrant.getId()指的是微服务的instanceId,gMap为内层map
Lease<InstanceInfo> existingLease = gMap.get(registrant.getId());

关键在下面
问题:为什么明明是新注册的,这块却要判断existingLease != null?(按理说一定为null啊)

if (existingLease != null && (existingLease.getHolder() != null)) {
    Long existingLastDirtyTimestamp = existingLease.getHolder().getLastDirtyTimestamp();
    Long registrationLastDirtyTimestamp = registrant.getLastDirtyTimestamp();
    // 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) {
        registrant = existingLease.getHolder();
    }
}

答:有三种情况会出现在注册时,服务端注册表已经有过注册信息了
1. 场景一:当客户端将注册请求发给服务端后等待服务端响应,服务端接收到了请求后完成了注册并且给客户端响应,但是由于网络原因,客户端并没有接收到来自服务端的响应,这时客户端(由于超时)会再次向服务端发出注册请求,第二次注册请求到达后,会发现注册表中已经存在了
2. 场景二:当客户端的instanceinfo信息发生变化后,其客户端有一个定时任务是将修改后的作为新的instanceinfo向服务端发送注册请求,该请求到达服务端后,这时注册表中信息是已经存在的。
3. 场景三:当一个客户端提交了注册请求后,紧接着客户端就向服务端发送心跳,由于网络原因,在注册请求还未到达服务端之前,心跳就已经到达了服务端,此时服务端会向客户端发送NOT_FOUND响应,客户端接收到了NOT_FOUND响应后,会发送注册请求,此时无论哪个注册请求先到达服务端,后到达的注册请求都会发现注册表中已经有了注册信息。

继续向下

// 如果服务端注册表中存在
if (existingLease != null && (existingLease.getHolder() != null)) {
    // 获取到注册表中已经存在的客户端对InstanceInfo最新修改时间
    Long existingLastDirtyTimestamp = existingLease.getHolder().getLastDirtyTimestamp();
    // 获取到注册请求中的客户端对InstanceInfo最新修改时间
    Long registrationLastDirtyTimestamp = registrant.getLastDirtyTimestamp();
    // InstanceInfo instead of the server local copy.
    // 如果存在的大于注册的,则说明存在的比注册的要新(时间戳越大越新)
    // 场景二会出现这种情况(请求比心跳慢)
    if (existingLastDirtyTimestamp > registrationLastDirtyTimestamp) {
    	// 最终不管谁大,都会将最新的赋值给注册的
        registrant = existingLease.getHolder();
    }
}

继续往下

// 可以走到else,说明这的确是一个新的注册请求
else {
    synchronized (lock) {
        if (this.expectedNumberOfClientsSendingRenews > 0) {
            this.expectedNumberOfClientsSendingRenews = this.expectedNumberOfClientsSendingRenews + 1;
            updateRenewsPerMinThreshold();
        }
    }
}

先说一下expectedNumberOfClientsSendingRenews ,在配置文件中的配置是,翻译过来叫期望客户端发送续约的数量(默认值是1)
在这里插入图片描述

// 由于没有设置所以默认值是1,大于0.if条件成立
if (this.expectedNumberOfClientsSendingRenews > 0) {
	// 这里为什么要+1?后续会给出答案
    this.expectedNumberOfClientsSendingRenews = this.expectedNumberOfClientsSendingRenews + 1;
    updateRenewsPerMinThreshold();
}

跟进updateRenewsPerMinThreshold()

protected void updateRenewsPerMinThreshold() {
    this.numberOfRenewsPerMinThreshold = (int) (this.expectedNumberOfClientsSendingRenews
            * (60.0 / serverConfig.getExpectedClientRenewalIntervalSeconds())
            * serverConfig.getRenewalPercentThreshold());
}

这里面的renewsPerMinThreshold是图中标注出的值:
Renews threshold:Eureka Server 期望每分钟收到的心跳数量
Renews(last min):Eureka Server 实际在最近一分钟内收到的心跳数量
若 Renews(last min) < Renews threshold,则启用自我保护
在这里插入图片描述
我们分析这个公式

(int) (this.expectedNumberOfClientsSendingRenews
            * (60.0 / serverConfig.getExpectedClientRenewalIntervalSeconds())
            * serverConfig.getRenewalPercentThreshold());

this.expectedNumberOfClientsSendingRenews:期望客户端发送心跳的数量
serverConfig.getExpectedClientRenewalIntervalSeconds():期望客户端多长时间给服务端发送一次续约,默认是30秒
在这里插入图片描述
serverConfig.getRenewalPercentThreshold():设置开启自我保护的阈值,默认0.85
在这里插入图片描述
最终表示的公式应该是这样子的
在这里插入图片描述
注意:客户端数量为啥是2?原因是

// 默认为1
this.expectedNumberOfClientsSendingRenews = this.expectedNumberOfClientsSendingRenews + 1;

(60 / 续约间隔):指的是一个客户端1分钟应该给服务端发送的续约次数。
也就是说,最终计算出来的numberOfRenewsPerMinThreshold这个值,如果将来收到的心跳值低于这个值,则会开启自我保护机制

// 这里面为什么要+1
this.expectedNumberOfClientsSendingRenews = this.expectedNumberOfClientsSendingRenews + 1;

原因是:为了提高系统的安全性,额外加了一台机器相当于门槛就提高了,比如说我原本收到80个就够了,由于现在门槛提高了我必须收到90个才行,收不到90个就要启动自我保护机制了,这样系统的可用性会更高。
继续往下

// 创建续约信息,这时候的registrant肯定是最新的
Lease<InstanceInfo> lease = new Lease<InstanceInfo>(registrant, leaseDuration);
if (existingLease != null) {
    // 如果服务端注册表中不为空,则更新一下服务UP状态的时间
    lease.setServiceUpTimestamp(existingLease.getServiceUpTimestamp());
}
// 将注册的客户端放入到注册表中(内层map)
gMap.put(registrant.getId(), lease);
synchronized (recentRegisteredQueue) {
	// 将新注册的客户端添加到一个队列中,这是以后给monitor用的
    recentRegisteredQueue.add(new Pair<Long, String>(
            System.currentTimeMillis(),
            registrant.getAppName() + "(" + registrant.getId() + ")"));
}

继续向下

// 如果注册的客户端状态不是UNKNOWN
// This is where the initial state transfer of overridden status happens
if (!InstanceStatus.UNKNOWN.equals(registrant.getOverriddenStatus())) {
	// overriddenInstanceStatusMap是一个instanceId与状态相对应的map
	// 查看这个map中有没有当前客户端
    if (!overriddenInstanceStatusMap.containsKey(registrant.getId())) { 
    	// 没有的话,将当前注册的客户端的instanceId与状态信息放入map
        overriddenInstanceStatusMap.put(registrant.getId(), registrant.getOverriddenStatus());
    }
}
// 取出instanceId对应的状态
InstanceStatus overriddenStatusFromMap = overriddenInstanceStatusMap.get(registrant.getId());
// 将最新的状态同步到注册的客户端(这时这个客户端是已经在注册表中的)
if (overriddenStatusFromMap != null) {
    registrant.setOverriddenStatus(overriddenStatusFromMap);
}

InstanceStatus指的是状态信息
在这里插入图片描述
以下的代码全部都是跟状态相关的,我们这里就不作细致分析了。
这时注册到服务端注册表完结了,下面开始分析同步到其他服务端,代码回到下图所示
在这里插入图片描述
跟进replicateToPeers()

// 传入的action为register注册操作
private void replicateToPeers(Action action, String appName, String id,
                                  InstanceInfo info,
                                  InstanceStatus newStatus, boolean isReplication) {
    // 此处是为monitor服务的,不需要关注
    Stopwatch tracer = action.getTimer().start();
    try {
    	// 同样是计数
        if (isReplication) {
            numberOfReplicationsLastMin.increment();
        }
        // 如果server集合为空或者不是server间复制,则直接退出
        if (peerEurekaNodes == Collections.EMPTY_LIST || isReplication) {
            return;
        }
        // 从eureka server集群中,每取出一台进行遍历
        for (final PeerEurekaNode node : peerEurekaNodes.getPeerEurekaNodes()) {
        	// 如果当前的node是本机,则继续下一次循环(排除掉自己)
            if (peerEurekaNodes.isThisMyUrl(node.getServiceUrl())) {
                continue;
            }
            // 进行复制操作,注意action是register
            replicateInstanceActionsToPeers(action, appName, id, info, newStatus, node);
        }
    } finally {
        tracer.stop();
    }
}

跟进replicateInstanceActionsToPeers(),由于传进来的action是register所以
在这里插入图片描述
跟进node.register(info)

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() {
                return replicationClient.register(info);
            }
        },
        expiryTime
    );
}

首先看expiryTime这个时间:当前时间 + 过期时间

long expiryTime = System.currentTimeMillis() + getLeaseRenewalOf(info);
private static int getLeaseRenewalOf(InstanceInfo info) {
	// 如果压根就没有配置过续约信息默认取90秒,否则取自己配置的,最后变为毫秒
    return (info.getLeaseInfo() == null ? Lease.DEFAULT_DURATION_IN_SECS : info.getLeaseInfo().getRenewalIntervalInSecs()) * 1000;
}
// 这里指的是在expiryTime时间之前,必须要完成register任务
batchingDispatcher.process(
        taskId("register", info),
        new InstanceReplicationTask(targetHost, Action.Register, info, null, true) {
            public EurekaHttpResponse<Void> execute() {
                return replicationClient.register(info);
            }
        },
        expiryTime
);

跟进replicationClient.register(info),这个方法就非常熟悉了
在这里插入图片描述

3、处理服务续约

处理服务续约处理器为:InstanceResource,其中renewLease()方法是整个处理续约流程的入口
在这里插入图片描述
主要任务:
1. 客户端向服务端发送心跳,服务端接收到心跳后,修改其在注册表中的状态
2. 将心跳同步到其他服务端

// isReplication为null,isFromReplicaNode 为false
boolean isFromReplicaNode = "true".equals(isReplication);
boolean isSuccess = registry.renew(app.getName(), id, isFromReplicaNode);

跟进renew()
在这里插入图片描述

@Override
public boolean renew(final String appName, final String serverId,
		boolean 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);
}

跟进super.renew()

public boolean renew(final String appName, final String id, final boolean isReplication) {
	// 更新状态
    if (super.renew(appName, id, isReplication)) {
    	// 同步到其他server
        replicateToPeers(Action.Heartbeat, appName, id, null, null, isReplication);
        return true;
    }
    return false;
}

首先跟进super.renew()

public boolean renew(String appName, String id, boolean isReplication) 
	// 计数
	RENEW.increment(isReplication);
	// 从注册表中获取到内层map
    Map<String, Lease<InstanceInfo>> gMap = registry.get(appName);
    Lease<InstanceInfo> leaseToRenew = null;
    if (gMap != null) {
    	// 内层map不为空,获取对应的主机(Lease续约对象)
        leaseToRenew = gMap.get(id);
    }
    if (leaseToRenew == null) {
    	// 如果注册表中找不到,则返回false
        RENEW_NOT_FOUND.increment(isReplication);
        return false;
    }
else {
	// 说明在服务端注册表有当前续约对象
	// 获取InstanceInfo对象
   InstanceInfo instanceInfo = leaseToRenew.getHolder();
   if (instanceInfo != null) {
       // touchASGCache(instanceInfo.getASGName());
       // 获取该instanceinfo最新的可覆盖状态
       InstanceStatus overriddenInstanceStatus = this.getOverriddenInstanceStatus(
               instanceInfo, leaseToRenew, isReplication);
       // 若状态是UNKNOWN,表示心跳失败,返回false
       if (overriddenInstanceStatus == InstanceStatus.UNKNOWN) {
           RENEW_NOT_FOUND.increment(isReplication);
           return false;
       }
       // 如果说该instanceinfo的实例状态与其最新的可覆盖状态不同
       if (!instanceInfo.getStatus().equals(overriddenInstanceStatus)) {
			//应该设置成最新的可覆盖状态
           	instanceInfo.setStatusWithoutDirty(overriddenInstanceStatus);
       }
   }
   // 计数
   renewsLastMin.increment();
   // 这里面实际上是将Lease对象的lastUpdateTimestamp(最新修改时间)修改成
   // System.currentTimeMillis() + duration(当前时间 + 续约间隔)
   leaseToRenew.renew();
   return true;
}

下面继续跟同步代码,这个时候action是Action.Heartbeat
在这里插入图片描述
跟进,相同的代码,直接看replicateInstanceActionsToPeers(),action是Heartbeat
在这里插入图片描述
跟进
在这里插入图片描述

case Heartbeat:
	 // 获取最新状态
     InstanceStatus overriddenStatus = overriddenInstanceStatusMap.get(id);
     // 获取instanceinfo信息
     infoFromRegistry = getInstanceByAppAndId(appName, id, false);
     node.heartbeat(appName, id, infoFromRegistry, overriddenStatus, false);
     break;

跟进heartbeat()

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) {
	// 这里最重要的是关注sendHeartBeat()这个方法
    @Override
    public EurekaHttpResponse<InstanceInfo> execute() throws Throwable {
        return replicationClient.sendHeartBeat(appName, id, info, overriddenStatus);
    }
	// 以下是处理失败,不需要关注
    @Override
    public void handleFailure(int statusCode, Object responseEntity) throws Throwable {
        super.handleFailure(statusCode, responseEntity);
        if (statusCode == 404) {
            logger.warn("{}: missing entry.", getTaskName());
            if (info != null) {
                logger.warn("{}: cannot find instance id {} and hence replicating the instance with status {}",
                        getTaskName(), info.getId(), info.getStatus());
                register(info);
            }
        } else if (config.shouldSyncWhenTimestampDiffers()) {
            InstanceInfo peerInstanceInfo = (InstanceInfo) responseEntity;
            if (peerInstanceInfo != null) {
                syncInstancesIfTimestampDiffers(appName, id, info, peerInstanceInfo);
            }
        }
    }
};

跟进sendHeartBeat()
在这里插入图片描述
在这里插入图片描述
这里需要注意这个String urlPath = “apps/” + appName + ‘/’ + id;
心跳发送的是put请求,在请求发送时会将服务名称以及instanceId发送给server端。server端会根据发过来的instanceId来查找自己的服务端列表,如果没有找到说明心跳失败,会返回NOT_FOUND信息。
在这里插入图片描述

4、处理服务下架

主要任务:
1. 客户端向服务端发送下架请求,服务端接收到请求后,将其从注册表的内层map中删除
2. 同步给其他server

同样还是在InstanceResource处理器中
在这里插入图片描述

boolean isSuccess = registry.cancel(app.getName(), id,
                "true".equals(isReplication));

跟进cancel()
在这里插入图片描述

@Override
public boolean cancel(String appName, String serverId, boolean isReplication) {
	// 发布事件,不关注
	handleCancelation(appName, serverId, isReplication);
	return super.cancel(appName, serverId, isReplication);
}

跟进super.cancel(),这里看依旧是两个步骤
在这里插入图片描述
先跟进super.cancel(appName, id, isReplication)

protected boolean internalCancel(String appName, String id, boolean isReplication) {
try {
      read.lock();
      // 计数
      CANCEL.increment(isReplication);
      // 根据服务名称获取内层map
      Map<String, Lease<InstanceInfo>> gMap = registry.get(appName);
      Lease<InstanceInfo> leaseToCancel = null;
      if (gMap != null) {
      	  // 如果内层map不为null,移除key为instanceId的value(Lease对象),并将其返回
          leaseToCancel = gMap.remove(id);
      }
      synchronized (recentCanceledQueue) {
          // 添加进最近下架的队列,供monitor使用
          recentCanceledQueue.add(new Pair<Long, String>(System.currentTimeMillis(), appName + "(" + id + ")"));
      }
      // 将此instanceId对应的信息从状态map中移除
      InstanceStatus instanceStatus = overriddenInstanceStatusMap.remove(id);

// leaseToCancel 为从内层map中remove掉的那个,也就是下架的Lease对象
if (leaseToCancel == null) {
	// 如果为空,返回false
    CANCEL_NOT_FOUND.increment(isReplication);
    return false;
} else {
	// 不为空 evictionTimestamp = System.currentTimeMillis();
	// 将下架时间设置成当前时间
    leaseToCancel.cancel();
    // 获取到下架的InstanceInfo
    InstanceInfo instanceInfo = leaseToCancel.getHolder();
    String vip = null;
    String svip = null;
    if (instanceInfo != null) {
    	// 改变状态
        instanceInfo.setActionType(ActionType.DELETED);
        // 添加队列,供monitor使用
        recentlyChangedQueue.add(new RecentlyChangedItem(leaseToCancel));
        instanceInfo.setLastUpdatedTimestamp();
        vip = instanceInfo.getVIPAddress();
        svip = instanceInfo.getSecureVipAddress();
    }
    // 缓存失效
    invalidateCache(appName, vip, svip);
    return true;
}

服务下架的本质实际上就是从内层Map中删除即可
同步这块的话,跟原来是一样的,只不过是发送delete请求,这块就不做分析了。
在这里插入图片描述

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值