注册中心 Eureka 源码解析 —— 应用实例注册发现(三)之下线

栏目: Java · 发布时间: 8年前

内容简介:注册中心 Eureka 源码解析 —— 应用实例注册发现(三)之下线

摘要: 原创出处 http://www.iocoder.cn/Eureka/instance-registry-cancel/ 「芋道源码」欢迎转载,保留摘要,谢谢!

本文主要基于 Eureka 1.8.X 版本

注册中心 Eureka 源码解析 —— 应用实例注册发现(三)之下线

关注 微信公众号:【芋道源码】 有福利:

  1. RocketMQ / MyCAT / Sharding-JDBC 所有 源码分析文章列表
  2. RocketMQ / MyCAT / Sharding-JDBC 中文注释源码 GitHub 地址
  3. 您对于源码的疑问每条留言 将得到 认真 回复。 甚至不知道如何读源码也可以请教噢
  4. 新的 源码解析文章 实时 收到通知。 每周更新一篇左右
  5. 认真的 源码交流微信群。

1. 概述

本文主要分享 Eureka-Client 向 Eureka-Server 下线应用实例的过程

  • 蓝框 部分,为本文重点。
  • 蓝框 部分,Eureka-Server 集群间复制注册的应用实例信息,不在本文内容范畴。

推荐 Spring Cloud 书籍:

2. Eureka-Client 发起下线

应用实例关闭时,Eureka-Client 向 Eureka-Server 发起下线应用实例。需要满足如下条件才可发起:

  • 配置 eureka.registration.enabled = true ,应用实例开启注册开关。默认为 false
  • 配置 eureka.shouldUnregisterOnShutdown = true ,应用实例开启关闭时下线开关。默认为 true

实现代码如下:

// DiscoveryClient.java
public synchronized void shutdown(){

 // ... 省略无关代码

 // If APPINFO was registered
 if (applicationInfoManager != null
 && clientConfig.shouldRegisterWithEureka() // eureka.registration.enabled = true
 && clientConfig.shouldUnregisterOnShutdown()) { // eureka.shouldUnregisterOnShutdown = true
 applicationInfoManager.setInstanceStatus(InstanceStatus.DOWN);
 unregister();
 }
}
  • 调用 ApplicationInfoManager#setInstanceStatus(...) 方法,设置应用实例为关闭( DOWN )。
  • 调用 #unregister() 方法,实现代码如下:

    // DiscoveryClient.java
    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);
     }
     }
    }
    
    // AbstractJerseyEurekaHttpClient.java
    @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();
     }
     }
    }
    
    • 调用 AbstractJerseyEurekaHttpClient#cancel(...) 方法, DELETE 请求 Eureka-Server 的 apps/${APP_NAME}/${INSTANCE_INFO_ID} 接口,实现应用实例信息的下线。

3. Eureka-Server 接收下线

3.1 接收下线请求

com.netflix.eureka.resources.InstanceResource ,处理 单个 应用实例信息的请求操作的 Resource ( Controller )。

下线应用实例信息的请求,映射 InstanceResource#cancelLease() 方法,实现代码如下:

@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();
 }
}
  • 调用 PeerAwareInstanceRegistryImpl#cancel(...) 方法,下线应用实例。实现代码如下:

     1: @Override
     2: public boolean cancel(final String appName, final String id,
    3: final boolean isReplication){
     4: if (super.cancel(appName, id, isReplication)) { // 下线
     5: // Eureka-Server 复制
     6: replicateToPeers(Action.Cancel, appName, id, null, null, isReplication);
     7: // 减少 `numberOfRenewsPerMinThreshold` 、`expectedNumberOfRenewsPerMin`
     8: synchronized (lock) {
     9: if (this.expectedNumberOfRenewsPerMin > 0) {
    10: // Since the client wants to cancel it, reduce the threshold (1 for 30 seconds, 2 for a minute)
    11: this.expectedNumberOfRenewsPerMin = this.expectedNumberOfRenewsPerMin - 2;
    12: this.numberOfRenewsPerMinThreshold = (int) (this.expectedNumberOfRenewsPerMin * serverConfig.getRenewalPercentThreshold());
    13: }
    14: }
    15: return true;
    16: }
    17: return false;
    18: }
    

3.2 下线应用实例信息

调用 AbstractInstanceRegistry#cancel(...) 方法,下线应用实例信息,实现代码如下:

 1: @Override
 2: public boolean cancel(String appName, String id, boolean isReplication){
 3: return internalCancel(appName, id, isReplication);
 4: }
 5: 
 6: protected boolean internalCancel(String appName, String id, boolean isReplication){
 7: try {
 8: // 获得读锁
 9: read.lock();
10: // 增加 取消注册次数 到 监控
11: CANCEL.increment(isReplication);
12: // 移除 租约映射
13: Map<String, Lease<InstanceInfo>> gMap = registry.get(appName);
14: Lease<InstanceInfo> leaseToCancel = null;
15: if (gMap != null) {
16: leaseToCancel = gMap.remove(id);
17: }
18: // 添加到 最近取消注册的调试队列
19: synchronized (recentCanceledQueue) {
20: recentCanceledQueue.add(new Pair<Long, String>(System.currentTimeMillis(), appName + "(" + id + ")"));
21: }
22: // 移除 应用实例覆盖状态映射
23: InstanceStatus instanceStatus = overriddenInstanceStatusMap.remove(id);
24: if (instanceStatus != null) {
25: logger.debug("Removed instance id {} from the overridden map which has value {}", id, instanceStatus.name());
26: }
27: // 租约不存在
28: if (leaseToCancel == null) {
29: CANCEL_NOT_FOUND.increment(isReplication); // 添加 取消注册不存在 到 监控
30: logger.warn("DS: Registry: cancel failed because Lease is not registered for: {}/{}", appName, id);
31: return false; // 失败
32: } else {
33: // 设置 租约的取消注册时间戳
34: leaseToCancel.cancel();
35: // 添加到 最近租约变更记录队列
36: InstanceInfo instanceInfo = leaseToCancel.getHolder();
37: String vip = null;
38: String svip = null;
39: if (instanceInfo != null) {
40: instanceInfo.setActionType(ActionType.DELETED);
41: recentlyChangedQueue.add(new RecentlyChangedItem(leaseToCancel));
42: instanceInfo.setLastUpdatedTimestamp();
43: vip = instanceInfo.getVIPAddress();
44: svip = instanceInfo.getSecureVipAddress();
45: }
46: // 设置 响应缓存 过期
47: invalidateCache(appName, vip, svip);
48: logger.info("Cancelled instance {}/{} (replication={})", appName, id, isReplication);
49: return true; // 成功
50: }
51: } finally {
52: // 释放锁
53: read.unlock();
54: }
55: }
  • 第 9 行 :获取读锁。在 《Eureka源码解析 —— 应用实例注册发现 (九)之岁月是把萌萌的读写锁》 详细解析。
  • 第 10 至 11 行 :增加下线次数到监控。配合 Netflix Servo 实现监控信息采集。
  • 第 12 至 17 行 :移除租约映射( registry )。
  • 第 18 至 21 行 :添加到最近下线的 调试 队列( recentCanceledQueue ),用于 Eureka-Server 运维界面的显示,无实际业务逻辑使用。实现代码如下:

    /**
    * 最近取消注册的调试队列
    * key :添加时的时间戳
    * value :字符串 = 应用名(应用实例信息编号)
    */
    private final CircularQueue<Pair<Long, String>> recentCanceledQueue;
    
  • 第 22 至 26 行 :移除应用实例覆盖状态映射。在 《应用实例注册发现 (八)之覆盖状态》 详细解析。

  • 第 27 至 31 行 :租约不存在,返回下线失败( false )。
  • 第 34 行 :调用 Lease#cancel() 方法,取消租约。实现代码如下:

    // Lease.java
    public void cancel(){
     if (evictionTimestamp <= 0) {
     evictionTimestamp = System.currentTimeMillis();
     }
    }
    
  • 第 35 至 45 行 :设置应用实例信息的 操作类型为添加 ,并添加到最近租约变更记录队列( recentlyChangedQueue )。 recentlyChangedQueue 用于注册信息的 增量 获取,在 《应用实例注册发现 (七)之增量获取》 详细解析。实现代码如下:

    /**
    * 最近租约变更记录队列
    */
    private ConcurrentLinkedQueue<RecentlyChangedItem> recentlyChangedQueue = new ConcurrentLinkedQueue<RecentlyChangedItem>();
    
  • 第 47 行 :设置响应缓存( ResponseCache )过期,在 《Eureka 源码解析 —— 应用实例注册发现 (六)之全量获取》 详细解析。

  • 第 49 行 :返回下线失败( false )。
  • 第 53 行 :释放锁。

666. 彩蛋

水更一篇,下一篇 租约过期 !走起。

胖友,分享我的公众号( 芋道源码 ) 给你的胖友可好?


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Head First Python

Head First Python

Paul Barry / O'Reilly Media / 2010-11-30 / USD 49.99

Are you keen to add Python to your programming skills? Learn quickly and have some fun at the same time with Head First Python. This book takes you beyond typical how-to manuals with engaging images, ......一起来看看 《Head First Python》 这本书的介绍吧!

HTML 压缩/解压工具
HTML 压缩/解压工具

在线压缩/解压 HTML 代码

XML 在线格式化
XML 在线格式化

在线 XML 格式化压缩工具

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具