Eureka主动下线机制

本文来说下Eureka主动下线机制


.

eureka几种主动下线服务的方式

直接停掉服务

默认情况下,如果Eureka Server在90秒没有收到Eureka客户的续约,它会将实例从其注册表中删除。但这种做法的不好之处在于, 客户端已经停止了运行,但仍然在注册中心的列表中。 虽然通过一定的负载均衡策略或使用熔断器可以让服务正常进行,但有没有方法让注册中心马上知道服务已经下线呢?


向eureka注册中心发送delete请求

为了让注册中心马上知道服务要下线, 可以向eureka 注册中心发送delete 请求

格式为 /eureka/apps/{application.name}/

在这里插入图片描述
在这里插入图片描述
值得注意的是,Eureka客户端每隔一段时间(默认30秒)会发送一次心跳到注册中心续约。如果通过这种方式下线了一个服务,而没有及时停掉的话,该服务很快又会回到服务列表中。


客户端主动通知注册中心下线

如果你的eureka客户端是是一个spring boot应用,可以通过调用以下代码通知注册中心下线

@RestController
public class HelloController {

   @Autowired
   private DiscoveryClient client;
 
   @RequestMapping(value = "/hello", method = RequestMethod.GET)
   public String index() {
    
      List<ServiceInstance> instances = client.getInstances("hello-service"); 
      return "Hello World";
   }
 
   @RequestMapping(value = "/offline", method = RequestMethod.GET)
   public void offLine(){
   
      DiscoveryManager.getInstance().shutdownComponent();
   } 
}

程序入口

com.netflix.discovery.DiscoverClient

 @PreDestroy
 @Override
 public synchronized void shutdown() {
     if (isShutdown.compareAndSet(false, true)) {
         logger.info("Shutting down DiscoveryClient ...");
 
         if (statusChangeListener != null && applicationInfoManager != null) {
             applicationInfoManager.unregisterStatusChangeListener(statusChangeListener.getId());
         }
        // 取消定时任务(心跳,缓存刷新等)
        cancelScheduledTasks();

        // 如果app注册过,那么需要取消注册,也就说需要主动下线
       if (applicationInfoManager != null && clientConfig.shouldRegisterWithEureka()) {
             // 设置实例的状态为DOWN
            applicationInfoManager.setInstanceStatus(InstanceStatus.DOWN);
            // 执行下线
            unregister();
        }

        if (eurekaTransport != null) {
            eurekaTransport.shutdown();
        }

        heartbeatStalenessMonitor.shutdown();
        registryStalenessMonitor.shutdown();

        logger.info("Completed shut down of DiscoveryClient");
    }
}


在这个类被容器销毁的时候,会执行这个方法,执行主动下线的代码

unregister()

void unregister() {

     // 如果是非注册的实例,那么eurekaTransport可能是为空的,所以做一下判断
     if(eurekaTransport != null && eurekaTransport.registrationClient != null) {
         try {
             logger.info("Unregistering ...");
             // 发送HTTP请求,到服务端,请求下线 , 接口地址:  "apps/" + appName + '/' + id;
             EurekaHttpResponse<Void> httpResponse = eurekaTransport.registrationClient.cancel(instanceInfo.getAppName(), instanceInfo.getId());
             logger.info(PREFIX + appPathIdentifier + " - deregister  status: " + httpResponse.getStatusCode());
         } catch (Exception e) {
            logger.error(PREFIX + appPathIdentifier + " - de-registration failed" + e.getMessage(), e);
        }
    }
}

Eureka-Server

com.netflix.eureka.resources.InstanceResource

@DELETE
public Response cancelLease(
         @HeaderParam(PeerEurekaNode.HEADER_REPLICATION) String isReplication) {
     //执行下线请求
     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();
    }
}

org.springframework.cloud.netflix.eureka.server.InstanceRegistry

@Override
public boolean cancel(String appName, String serverId, boolean isReplication) {

    //发布取消事件
    handleCancelation(appName, serverId, isReplication);
    // 调用父类的取消方法
    return super.cancel(appName, serverId, isReplication);
 }
 
 private void handleCancelation(String appName, String id, boolean isReplication) {
 
    log("cancel " + appName + ", serverId " + id + ", isReplication " + isReplication);
   publishEvent(new EurekaInstanceCanceledEvent(this, appName, id, isReplication));
}

父类PeerAwareInstanceRegistryImpl

@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);
         // Eureka-Server的保护机制
         synchronized (lock) {
            if (this.expectedNumberOfRenewsPerMin > 0) {
                // Since the client wants to cancel it, reduce the threshold (1 for 30 seconds, 2 for a minute)
                this.expectedNumberOfRenewsPerMin = this.expectedNumberOfRenewsPerMin - 2;
                this.numberOfRenewsPerMinThreshold =
                        (int) (this.expectedNumberOfRenewsPerMin * serverConfig.getRenewalPercentThreshold());
            }
        }
        return true;
    }
    return false;
}

PeerAwareInstanceRegistryImpl 的父类AbstractInstanceRegistry中是取消下线的主要逻辑。

@Override
public boolean cancel(String appName, String id, boolean isReplication) {

     return internalCancel(appName, id, isReplication);
 }
 
 /**
  * {@link #cancel(String, String, boolean)} method is overridden by {@link PeerAwareInstanceRegistry}, so each
  * cancel request is replicated to the peers. This is however not desired for expires which would be counted
  * in the remote peers as valid cancellations, so self preservation mode would not kick-in.
 */

protected boolean internalCancel(String appName, String id, boolean isReplication) {
    try {
        // 读锁
        read.lock();
        // 添加取消次数给监控信息,这里是个枚举类,收集了取消次数
        CANCEL.increment(isReplication);
        // 从本地的CurrentHashMap中,获取当前实例对应的Lease信息
        Map<String, Lease<InstanceInfo>> gMap = registry.get(appName);
        Lease<InstanceInfo> leaseToCancel = null;
        if (gMap != null) {
            // 移除信息 , 如果客户端是集群模式,此处仅移除这个实例ID对应的信息
            leaseToCancel = gMap.remove(id);
        }
        // 添加取消信息到取消队列,主要用于运维界面的信息统计
        synchronized (recentCanceledQueue) {
            recentCanceledQueue.add(new Pair<Long, String>(System.currentTimeMillis(), appName + "(" + id + ")"));
        }
        //移除这个实例ID对应的instance状态
        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 {
            // 更新Lease实例信息里面的evictionTimestamp这个时间戳,标明下线时间
            leaseToCancel.cancel();
            InstanceInfo instanceInfo = leaseToCancel.getHolder();
            String vip = null;
            String svip = null;
            // 获取VIP,SVIP,然后把instance的变化加入实例变化队列中
            if (instanceInfo != null) {
                instanceInfo.setActionType(ActionType.DELETED);
                recentlyChangedQueue.add(new RecentlyChangedItem(leaseToCancel));
                instanceInfo.setLastUpdatedTimestamp();
                vip = instanceInfo.getVIPAddress();
                svip = instanceInfo.getSecureVipAddress();
            }
            // 显示的清楚缓存 , guava的API
            invalidateCache(appName, vip, svip);
            logger.info("Cancelled instance {}/{} (replication={})", appName, id, isReplication);
            return true;
        }
    } finally {
        read.unlock();
    }
}

综上可以看到,首先是从本地的CurrentHashMap中获取当前appName对应的的Map信息,最后通过机器ID,获取要下线的机器对应的Lease,修改Lease的evictionTimestamp , 也就是设置下线时间为当前时间点。

public void cancel() {

    if (evictionTimestamp <= 0) {
        evictionTimestamp = System.currentTimeMillis();
    }
}

主动下线还是比较简单的。


本文小结

本文详细介绍了Eureka主动下线的主要源码和机制。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值