Su的技术博客

  • 首页
  • Java
  • MySQL
  • DDD
  • 事故复盘
  • 架构方案
  • AI
  • Other
  • 工具
  • 打赏
  • 关于
路很长,又很短
  1. 首页
  2. Java
  3. 正文
                           

【原创】Eureka源码剖析之五:服务下线

2020-01-26 152点热度 0人点赞 0条评论

 

现在研究下Eureka服务下线的源码。由服务续约的源码我们知道,如果客户端在90秒内没有继续跟服务端进行心跳的话,服务端会进行下线客户端并且更改状态将其剔除,并且也会在集群中告知(同步)其它节点。

 

〓Eureka Client
/**
 * 注销服务,调用client的cancel服务,往里面看也就是调用了服务端的http delete 请求进行服务下线
 */
void unregister() {
    // It can be null if shouldRegisterWithEureka == false
    if(eurekaTransport != null && eurekaTransport.registrationClient != null) {
        try {
            logger.info("Unregistering ...");
            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);
        }
    }
}

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

 

接下来再看是哪里调用注销服务:
@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();

         // 服务实例以及被注册,那么设置实例状态为DOWN,并且进行注销操作
         if (applicationInfoManager != null && clientConfig.shouldRegisterWithEureka()) {
             applicationInfoManager.setInstanceStatus(InstanceStatus.DOWN);
             unregister();
         }
         // 关闭eurekaTransport
         if (eurekaTransport != null) {
             eurekaTransport.shutdown();
         }
         // 关闭监控
         heartbeatStalenessMonitor.shutdown();
         registryStalenessMonitor.shutdown();

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

 // DiscoveryManager调用了DiscveryClient的shutdown方法,这里就是服务下线的入口
 public void shutdownComponent() {
     if (discoveryClient != null) {
         try {
             discoveryClient.shutdown();
             discoveryClient = null;
         } catch (Throwable th) {
             logger.error("Error in shutting down client", th);
         }
     }
 }
〓Eureka Server
// 服务端提供的对外服务下线接口
@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();
    }
}

// 服务端下线入口
@Override
public boolean cancel(final String appName, final String id,
                      final boolean isReplication) {
    // 调用父类cancel方法,将实例信息添加到最近下线队列、最近变更队列,并且使本地缓存失效操作
    if (super.cancel(appName, id, isReplication)) {
        // 服务端集群之间进行Cancel同步操作
        replicateToPeers(Action.Cancel, appName, id, null, null, isReplication);
        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;
}

// 最终调用这个方法进行实例信息移除
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);
        }
        // 添加到近期取消队列
        recentCanceledQueue.add(new Pair<Long, String>(System.currentTimeMillis(), appName + "(" + id + ")"));
        // 移除实例状态
        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) {
            // 续约实例不存在,返回false
            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 = leaseToCancel.getHolder();
            String vip = null;
            String svip = null;
            if (instanceInfo != null) {
                // 设置实例行为为delete
                instanceInfo.setActionType(ActionType.DELETED);
                // 添加到近期变化队列
                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);
            return true;
        }
    } finally {
        // 释放读锁
        read.unlock();
    }
}

 

总结

Eureka client执行DiscoveryManager调用了DiscveryClient的shutdown方法进行服务的下线操作,然后服务端接收到http delete请求之后进行服务的相关下线操作,并且同步到集群中的其它节点。
Eureka server则会将实例信息进行剔除处理,并且添加到近期变化队列和近期取消队列里。
 


 

本文仅供学习!所有权归属原作者。侵删!文章来源: 搬运工来架构

更多文章:

  1. Eureka源码剖析之一:初始化-启动
  2. DDD系列第五讲:聊聊如何避免写流水账代码
  3. Java后端18种接口优化技巧
  4. Eureka源码剖析之三:服务拉取
  5. Eureka源码剖析之四:服务续约
  6. Eureka源码之二:服务注册
  7. 殷浩详解DDD系列 第一讲 - Domain Primitive
  8. 殷浩详解DDD 第四讲:领域层设计规范
标签: 原创 开源框架 源码 eureka 注册中心
最后更新:2023-02-25

Cocodroid

专注Java后端,分享技术。

打赏 点赞
< 上一篇
下一篇 >

文章评论

razz evil exclaim smile redface biggrin eek confused idea lol mad twisted rolleyes wink cool arrow neutral cry mrgreen drooling persevering
取消回复

最新 热点 推荐
最新 热点 推荐
单体分层应用架构剖析 MySQL事务死锁问题排查 浅谈DDD中的聚合 高并发场景下JVM调优实践之路 ChatGPT的探索与实践 生产环境的CMS垃圾回收,一定要这样配置参数
Log4j框架疯狂写日志,导致磁盘打满问题排查高并发场景下JVM调优实践之路4.架构风格 vs. 架构模式 vs. 设计模式(译)5.单体架构(译)6.分层架构(译)ChatGPT的探索与实践
历经 16 年猪八戒网如何成功实现双活流量架构 Log4j框架疯狂写日志,导致磁盘打满问题排查 7. MVC及其变种(译) iOS请求访问文件网关服务图片接口异常问题的解决 实现一个状态机引擎,教你看清DSL的本质 3.编程语言的演化(译)

AIGC (1) BASE (1) bigkey (1) CAP (1) codeium (2) Copilot (2) hotkey (1) inject (1) jar包 (1) mvc (1) OOP (1) UML (1) vivo (2) 事务隔离级别 (1) 人工智能 (2) 代码质量 (1) 低耦合 (1) 依赖倒置原则 (1) 六边形架构 (1) 分层架构 (3) 分布式事务 (1) 分页 (1) 单体架构 (2) 可复用性 (1) 可读性 (1) 合同 (1) 后端开发 (1) 命名 (1) 四色建模法 (1) 垃圾回收器 (1) 开源 (1) 性能调优 (4) 智能助手 (1) 架构模式 (1) 架构设计 (4) 架构风格 (1) 模块 (1) 死锁 (1) 物流 (1) 系统架构 (4) 缓存穿透 (1) 缓存雪崩 (1) 编程助手 (3) 编程技能 (1) 编程语言 (2) 聚合 (1) 软件工程师 (1) 软件架构 (2) 驱动升级 (1) 高内聚 (1)

COPYRIGHT © 2014-2023 verysu.com . ALL RIGHTS RESERVED.

Theme Kratos Made By Seaton Jiang

粤ICP备15033072号-2