SpringCloud源码学习笔记之Eureka服务端——取消租约

1、前言

  在《Eureka服务端——服务续约》中,我们了解了服务端处理续约的过程,其实取消租约的过程和续约过程也是类似的,入口也是在InstanceResource类中,我们这里就开始学习一下取消租约的过程。

2、取消租约的流程

  服务端取消租约的流程和续约流程类似,基本如下:

  1. 首先,从InstanceResource类的cancelLease()方法作为入口,接收处理取消租约的请求
  2. 然后,调用InstanceRegistry类的cancel()方法,主要实现了广播EurekaInstanceCanceledEvent事件消息
  3. 再然后,又调用了父类PeerAwareInstanceRegistryImpl的cancel()方法,这里主要在通过调用父类取消操作成功后,实现了集群间各节点的信息同步。
  4. 最后,调用AbstractInstanceRegistry抽象类的cancel()方法,这里是真正实现了服务租约取消的地方

3、取消租约请求的入口

  InstanceResource类的cancelLease()方法,是Eureka服务取消租约的入口,该方法最终还是通过调用registry.cancel()方法进行租约的取消。

@DELETE
 public Response cancelLease(@HeaderParam(PeerEurekaNode.HEADER_REPLICATION) String isReplication) {
     try {
     		//取消租约方法调用
         boolean isSuccess = registry.cancel(app.getName(), id,
             "true".equals(isReplication));

         if (isSuccess) {
             logger.debug("Found (Cancel): {} - {}", app.getName(), id);
             return Response.ok().build();
         } else {
             logger.info("Not Found (Cancel): {} - {}", app.getName(), id);
             return Response.status(Status.NOT_FOUND).build();
         }
     } catch (Throwable e) {
         logger.error("Error (cancel): {} - {}", app.getName(), id, e);
         return Response.serverError().build();
     }

}

4、InstanceRegistry类的cancel()方法

  在InstanceRegistry类的cancel()方法中,主要通过调用handleCancelation()方法,实现了EurekaInstanceCanceledEvent事件的广播,然后再调用父类的cancel()方法继续处理。

@Override
public boolean cancel(String appName, String serverId, boolean isReplication) {
	handleCancelation(appName, serverId, isReplication);
	return super.cancel(appName, serverId, isReplication);
}

  handleCancelation()方法主要用来处理广播事件。

private void handleCancelation(String appName, String id, boolean isReplication) {
	log("cancel " + appName + ", serverId " + id + ", isReplication "
			+ isReplication);
	publishEvent(new EurekaInstanceCanceledEvent(this, appName, id, isReplication));
}

5、PeerAwareInstanceRegistryImpl类的cancel()方法

  在PeerAwareInstanceRegistryImpl类的cancel()方法,这里主要在通过调用父类取消操作成功后,通过调用replicateToPeers()方法实现了集群间各节点的信息同步。具体实现如下:

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

  在cancel()方法中,通过调用replicateToPeers()方法,而在replicateToPeers()方法又调用了replicateInstanceActionsToPeers()方法,这里都是用来做集群节点间数据同步的方法,前面在《Eureka服务端——服务注册》博文中已经学习了。我们这里直接看replicateInstanceActionsToPeers()方法中关于取消租约的相关代码如下:

node.cancel(appName, id);
5.1、PeerEurekaNode类的cancel()方法

  通过上述代码,我们可以知道,实际上最终还是通过PeerEurekaNode的cancel()方法实现取消租约数据同步,在该方法中,首先构建了同步任务InstanceReplicationTask,然后再通过batchingDispatcher.process()方法执行该任务,具体实现如下:

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() {
                     return replicationClient.cancel(appName, id);
                 }

                 @Override
                 public void handleFailure(int statusCode, Object responseEntity) throws Throwable {
                     super.handleFailure(statusCode, responseEntity);
                     if (statusCode == 404) {
                         logger.warn("{}: missing entry.", getTaskName());
                     }
                 }
             },
             expiryTime
     );
 }

其中,关于batchingDispatcher.process()方法,主要用于处理任务InstanceReplicationTask对象,并执行其中的replicationClient.cancel()方法,详细请参考《Eureka 源码解析 —— 任务批处理》

5.2、AbstractJerseyEurekaHttpClient类的cancel()方法

  AbstractJerseyEurekaHttpClient类的cancel()方法,是基于Jersey构建的服务取消请求。

@Override
 public EurekaHttpResponse<Void> cancel(String appName, String id) {
     String urlPath = "apps/" + appName + '/' + id;
     ClientResponse response = null;
     try {
         Builder resourceBuilder = jerseyClient.resource(serviceUrl).path(urlPath).getRequestBuilder();
         addExtraHeaders(resourceBuilder);
         response = resourceBuilder.delete(ClientResponse.class);
         return anEurekaHttpResponse(response.getStatus()).headers(headersOf(response)).build();
     } finally {
         if (logger.isDebugEnabled()) {
             logger.debug("Jersey HTTP DELETE {}/{}; statusCode={}", serviceUrl, urlPath, response == null ? "N/A" : response.getStatus());
         }
         if (response != null) {
             response.close();
         }
     }
 }

6、AbstractInstanceRegistry类的cancel()方法

  最后通过调用AbstractInstanceRegistry抽象类的cancel()方法实现了服务租约的取消,这里是真正实现了服务租约取消的地方。在cancel()方法中,又调用了internalCancel()方法,如下所示:

@Override
public boolean cancel(String appName, String id, boolean isReplication) {
     return internalCancel(appName, id, isReplication);
}

  取消服务租约的逻辑,实际上都是在internalCancel()方法中实现的,具体如下:

protected boolean internalCancel(String appName, String id, boolean isReplication) {
    try {
        read.lock();
        //EurekaMonitors计数
        CANCEL.increment(isReplication);
        //获取appName对应应用的InstanceInfo集合
        Map<String, Lease<InstanceInfo>> gMap = registry.get(appName);
        Lease<InstanceInfo> leaseToCancel = null;
        if (gMap != null) {//获取需要取消租约的Lease<InstanceInfo>对象
            leaseToCancel = gMap.remove(id);
        }
        //维护取消租约的数据,存储到recentCanceledQueue队列中,用于统计或调试
        recentCanceledQueue.add(new Pair<Long, String>(System.currentTimeMillis(), appName + "(" + id + ")"));
        //移除overriddenInstanceStatusMap中该对象的重写状态
        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 instanceInfo = leaseToCancel.getHolder();
            String vip = null;
            String svip = null;
            if (instanceInfo != null) {
            	//设置instanceInfo对象的变更类型
                instanceInfo.setActionType(ActionType.DELETED);
                //在recentlyChangedQueue队列添加变更记录
                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);
        }
    } finally {
        read.unlock();
    }
	//更新续约的阈值
    synchronized (lock) {
        if (this.expectedNumberOfClientsSendingRenews > 0) {
            // Since the client wants to cancel it, reduce the number of clients to send renews.
            this.expectedNumberOfClientsSendingRenews = this.expectedNumberOfClientsSendingRenews - 1;
            updateRenewsPerMinThreshold();
        }
    }

    return true;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

姠惢荇者

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值