Su的技术博客

  • 首页
  • 原创
  • 视频
  • Java
  • MySQL
  • DDD
  • 事故复盘
  • 架构方案
  • AI
  • Other
  • 工具
    • AI工具集
    • 工具清单
    • JSON在线格式化
    • JSON在线比较
    • SQL在线格式化
  • 打赏
  • 关于
路很长,又很短
  1. 首页
  2. Java
  3. 正文
                           

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

2020-01-26 1216点热度 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. Eureka源码剖析之四:服务续约
  3. Eureka源码剖析之三:服务拉取
  4. Eureka源码之二:服务注册
  5. Eureka 客户端配置注册地址为什么要加eureka做后缀?
  6. 【进阶玩法】策略+责任链+组合实现合同签章
  7. RocketMQ消息回溯实践与解析
  8. 殷浩详解DDD 第四讲:领域层设计规范
  9. 全链路压测之影子库及ShardingSphere实现影子库源码剖析
  10. 殷浩详解DDD系列 第一讲 - Domain Primitive
标签: 原创 开源框架 源码 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
取消回复

广告
最新 热点 推荐
最新 热点 推荐
Anthropic Code with Claude 开发者大会:开启 AI Agent 新时代 视频笔记-微服务架构P4:必懂5种设计模式 视频笔记:微服务架构P4 设计模式:每服务数据库、API 网关和事件驱动架构 干货 | 论Elasticsearch数据建模的重要性 马蜂窝消息总线——面向业务的消息服务设计 基于 MySQL Binlog 实现可配置的异构数据同步 视频笔记:Google发布Agent2Agent协议 视频笔记:什么是微服务,为什么是微服务?
基于 MySQL Binlog 实现可配置的异构数据同步马蜂窝消息总线——面向业务的消息服务设计视频笔记:微服务架构P4 设计模式:每服务数据库、API 网关和事件驱动架构干货 | 论Elasticsearch数据建模的重要性视频笔记-微服务架构P4:必懂5种设计模式Anthropic Code with Claude 开发者大会:开启 AI Agent 新时代
log4j2同步日志引发的性能问题 Google Gemini技术报告要点提炼 万字长文全面了解学习Netty! 可插拔组件设计机制—SPI DDD四色建模法 3分钟掌握CQS和CQRS架构设计原则 腾讯的这道面试题,我懵了... —— Redis的hashtable是如何扩容的 Codeium:强大且免费的AI智能编程助手

CRUD (1) Event Sourcing (1) graphql (1) id (1) NoSQL (1) quarkus (1) rest (1) RocketMQ (2) Spring Boot (1) zk (1) zookeeper (1) 上下文 (1) 事务消息 (1) 二级缓存 (1) 值对象 (1) 关系数据库 (1) 分布式缓存 (1) 原子性 (1) 唯一ID (1) 商品 (1) 多对多 (1) 子域 (1) 字符集 (1) 客户端心跳 (1) 幂等 (2) 干货 (1) 并发 (1) 应用场景 (1) 应用架构图 (1) 康威定律 (2) 异步复制 (1) 微服务架构 (3) 总体方案 (1) 技术方案 (2) 技术架构 (2) 技术架构图 (1) 技能 (1) 持续集成 (1) 支撑域 (1) 故障恢复 (1) 数据架构图 (1) 方案选型 (1) 日记 (1) 服务发现 (1) 服务治理 (1) 服务注册 (2) 机房 (1) 核心域 (1) 泄漏 (1) 洋葱架构 (1) 消息队列 (5) 源码剖析 (1) 灰度发布 (1) 熔断 (1) 生态 (1) 画图工具 (1) 研发团队 (1) 线程 (2) 组织架构 (1) 缓存架构 (1) 编码 (1) 视频 (20) 读写分离 (1) 贵州 (1) 软件设计 (1) 迁移 (1) 通用域 (1) 集群化 (1) 雪花算法 (1) 顺序消息 (1)

推荐链接🔗
  • AI工具集
  • 工具箱🛠️

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

Theme Kratos Made By Seaton Jiang

粤ICP备15033072号-2