Eureka 系列(07)服务注册与主动下线

Eureka 系列(07)服务注册与主动下线

[TOC]

Spring Cloud 系列目录 - Eureka 篇

在上一篇 Eureka 系列(05)消息广播 中对 Eureka 消息广播的源码进行了分析,之后的几篇文章会具体分析本地服务注册、主动下线、心跳续约、自动过期等的实现机制。

  • PeerAwareInstanceRegistryImpl 负责集群内部消息通信。
  • AbstractInstanceRegistry 负责本地服务信息管理,这也是之后几篇文章关注的重点。

表1:Eureka OPEN API

资源 功能 url
ApplicationsResource 获取全部或增量服务实例信息 GET /apps
GET /apps/delta
ApplicationResource 1. 获取单个应用的信息
2. 注册实例信息
GET /apps/{appName}
POST /apps/{appName}
InstanceResource 服务实例的CURD:
1. 获取实例的信息
2.修改服务实例元信息
3. 删除实例信息,服务下线
4. 发送心跳
GET /apps/{appName}/{id}
PUT /apps/{appName}/{id}/metadata
DELETE /apps/{appName}/{id}
PUT /apps/{appName}/{id}
InstancesResource 直接根据实例id获取实例信息 GET /instances/{id}
PeerReplicationResource 集群内部批量数据同步 POST /peerreplication/batch
ServerInfoResource ??? POST /serverinfo/statusoverrides
StatusResource ??? GET /statusoverrides

注: 表示应用名称或服务id, 表示实例id。eg: http://localhost:8080/eureka/apps

1. 服务注册

1.1 服务实例注册流程

图1:Eureka 服务实例注册时序图

sequenceDiagram
participant ApplicationResource
participant PeerAwareInstanceRegistryImpl
participant AbstractInstanceRegistry
participant PeerEurekaNode
note over ApplicationResource: POST:/euraka/apps/{appName}<br/>addInstance(instanceInfo,isReplication)
ApplicationResource ->> PeerAwareInstanceRegistryImpl: 注册请求:register(instanceInfo,isReplication)
PeerAwareInstanceRegistryImpl ->> AbstractInstanceRegistry: 1. 本地数据更新: register(instanceInfo,leaseDuration,isReplication)
loop 同步到其它 Eureka Server 节点
PeerAwareInstanceRegistryImpl ->> PeerAwareInstanceRegistryImpl: 2.1 数据同步:replicateInstanceActionsToPeers
PeerAwareInstanceRegistryImpl ->> PeerEurekaNode: 2.2 register(instanceInfo) -> POST:/euraka/apps/{appName}
end

总结: Eureka Web 使用的是 Jersey 容器,服务注册的请求入口是 ApplicationResource 的 register 方法,请求的路径是 POST:/euraka/apps/{appName}

1.2 ApplicationResource

// ApplicationResource HTTP请求入口
@POST
@Consumes({"application/json", "application/xml"})
public Response addInstance(InstanceInfo info,
	@HeaderParam(PeerEurekaNode.HEADER_REPLICATION) String isReplication) {
    ... // 数据检验
    registry.register(info, "true".equals(isReplication));
    return Response.status(204).build();  // 204 to be backwards compatible
}

总结: ApplicationResource 入口主要是进行参数检验,主要的逻辑都委托给了 PeerAwareInstanceRegistryImpl 完成。

注意:isReplication 参数,如果是客户端的请求则为 false,表示需要将这个消息广播给其它服务器。如果是集群内部消息广播则为true,表示不再需要继续广播,否则会造成循环广播的问题。

// PeerAwareInstanceRegistryImpl 默认注册器实现
@Override
public void register(final InstanceInfo info, final boolean isReplication) {
    // 租约的过期时间,默认90秒
    int leaseDuration = Lease.DEFAULT_DURATION_IN_SECS;
    if (info.getLeaseInfo() != null && info.getLeaseInfo().getDurationInSecs() > 0) {
        // 如果客户端自定义了,那么以客户端为准
        leaseDuration = info.getLeaseInfo().getDurationInSecs();
    }
    // 本地注册
    super.register(info, leaseDuration, isReplication);
    // 消息广播
    replicateToPeers(Action.Register, info.getAppName(), info.getId(), info, null, isReplication);
}

总结: PeerAwareInstanceRegistryImpl 这个类应该都很熟悉了,主要是负责进行集群间内部通信的,其父类 AbstractInstanceRegistry 则负责本地服务信息管理,也是本文的研究重点。

1.3 AbstractInstanceRegistry

在 Eureka 中,服务注册信息存储在内存中,数据结构为 ConcurrentHashMap<String, Map<String, Lease<InstanceInfo>>> registry,Map 嵌套了两层,外层是的 Key 是 appName,内存的 Key 是 InstanceId。

private final ConcurrentHashMap<String, Map<String, Lease<InstanceInfo>>> registry
            = new ConcurrentHashMap<String, Map<String, Lease<InstanceInfo>>>();
public void register(InstanceInfo registrant, int leaseDuration, boolean isReplication) {
    try {
        read.lock();
        // 1. 获取该服务对应的所有服务实例,如果不存在就创建一个新的Map
        Map<String, Lease<InstanceInfo>> gMap = registry.get(registrant.getAppName());
        REGISTER.increment(isReplication);
        if (gMap == null) {
            final ConcurrentHashMap<String, Lease<InstanceInfo>> gNewMap = new ConcurrentHashMap<String, Lease<InstanceInfo>>();
            gMap = registry.putIfAbsent(registrant.getAppName(), gNewMap);
            if (gMap == null) {
                gMap = gNewMap;
            }
        }

        // 2. 两种情况:一是实例已经注册,二是实例没有注册
        Lease<InstanceInfo> existingLease = gMap.get(registrant.getId());
        // 2.1 实例已经注册,就需要PK,PK原则:谁最后一次更新就是谁赢
        //     也就是说如果已经注册的实例最近更新了,就不用重新更新了
        if (existingLease != null && (existingLease.getHolder() != null)) {
            Long existingLastDirtyTimestamp = existingLease.getHolder().getLastDirtyTimestamp();
            Long registrationLastDirtyTimestamp = registrant.getLastDirtyTimestamp();
            // 已经注册的实例PK赢了
            if (existingLastDirtyTimestamp > registrationLastDirtyTimestamp) {
                registrant = existingLease.getHolder();
            }
        // 2.2 没有注册,很好处理。更新注册的实例个数
        } else {
            synchronized (lock) {
                if (this.expectedNumberOfClientsSendingRenews > 0) {
                    this.expectedNumberOfClientsSendingRenews = this.expectedNumberOfClientsSendingRenews + 1;
                    updateRenewsPerMinThreshold();
                }
            }
        }

        // 3. 更新注册信息(核心步骤)
        Lease<InstanceInfo> lease = new Lease<InstanceInfo>(registrant, leaseDuration);
        if (existingLease != null) {
            lease.setServiceUpTimestamp(existingLease.getServiceUpTimestamp());
        }
        // (核心步骤)
        gMap.put(registrant.getId(), lease);
        // 添加到最近的注册队列里面去,以时间戳作为Key,名称作为value,主要是为了运维界面的统计数据
        synchronized (recentRegisteredQueue) {
            recentRegisteredQueue.add(new Pair<Long, String>(
                System.currentTimeMillis(),
                registrant.getAppName() + "(" + registrant.getId() + ")"));
        }

        // 4. 更新实例状态 InstanceStatus
        if (!InstanceStatus.UNKNOWN.equals(registrant.getOverriddenStatus())) {
            if (!overriddenInstanceStatusMap.containsKey(registrant.getId())) {
                overriddenInstanceStatusMap.put(registrant.getId(), registrant.getOverriddenStatus());
            }
        }
        InstanceStatus overriddenStatusFromMap = overriddenInstanceStatusMap.get(registrant.getId());
        if (overriddenStatusFromMap != null) {
            registrant.setOverriddenStatus(overriddenStatusFromMap);
        }

        InstanceStatus overriddenInstanceStatus = getOverriddenInstanceStatus(registrant, existingLease, isReplication);
        registrant.setStatusWithoutDirty(overriddenInstanceStatus);

        if (InstanceStatus.UP.equals(registrant.getStatus())) {
            lease.serviceUp();
        }

        // 5. 清理缓存等善后工作
        registrant.setActionType(ActionType.ADDED);
        // 租约变更记录队列,记录了实例的每次变化, 用于注册信息的增量获取
        recentlyChangedQueue.add(new RecentlyChangedItem(lease));
        registrant.setLastUpdatedTimestamp();
        // 清理缓存 ,传入的参数为key
        invalidateCache(registrant.getAppName(), registrant.getVIPAddress(), registrant.getSecureVipAddress());
    } finally {
        read.unlock();
    }
}

总结: 前三步的逻辑都很清楚,目的就是更新内存中的实例信息,只是需要注意实例已经存在的情况下需要 PK 一下,原则就是谁最后一次更新就是谁赢。

第四步更新服务实例的状态,OverriddenStatus 参考 InstanceInfo 中 OverriddenStatus 的作用

每五步清理缓存等善后工作。目前来说,到 gMap.put(registrant.getId(), lease)这一步就够了。

2. 主动下线

服务下线对应的 OPEN API 为 DELETE /apps/{appName}/{id}

protected boolean internalCancel(String appName, String id, boolean isReplication) {
    try {
        read.lock();
        CANCEL.increment(isReplication);
        // 1. 清空registry中注册的实例信息(核心步骤)
        Map<String, Lease<InstanceInfo>> gMap = registry.get(appName);
        Lease<InstanceInfo> leaseToCancel = null;
        if (gMap != null) {
            leaseToCancel = gMap.remove(id);
        }
        // 2. 添加到 recentCanceledQueue 队列中
        synchronized (recentCanceledQueue) {
            recentCanceledQueue.add(new Pair<Long, String>(System.currentTimeMillis(), appName + "(" + id + ")"));
        }
        InstanceStatus instanceStatus = overriddenInstanceStatusMap.remove(id);
        if (leaseToCancel == null) {
            CANCEL_NOT_FOUND.increment(isReplication);
            return false;
        } else {
            // 3. 和注册时一样,也要做一下清除缓存等善后工作
            leaseToCancel.cancel();
            InstanceInfo instanceInfo = leaseToCancel.getHolder();
            String vip = null;
            String svip = null;
            if (instanceInfo != null) {
                instanceInfo.setActionType(ActionType.DELETED);
                recentlyChangedQueue.add(new RecentlyChangedItem(leaseToCancel));
                instanceInfo.setLastUpdatedTimestamp();
                vip = instanceInfo.getVIPAddress();
                svip = instanceInfo.getSecureVipAddress();
            }
            invalidateCache(appName, vip, svip);
            return true;
        }
    } finally {
        read.unlock();
    }
}

总结: 服务的注册和主动下线的逻辑还是很清楚的。**目前来说,到 gMap.remove(id)这一步就够了。**至于细节以后真正用到 Eureka 再继续深入研究。



每天用心记录一点点。内容也许不重要,但习惯很重要!

原文地址:https://www.cnblogs.com/binarylei/p/11618566.html

时间: 2024-08-27 05:28:41

Eureka 系列(07)服务注册与主动下线的相关文章

Spring Cloud Eureka 2 (Eureka Server搭建服务注册中心)

工具:IntelliJ IDEA 2017.1.2 x64.maven3.3.9 打开IDE  file===>new===>project next next 选择相应的依赖 next finish 查看下上述我们选的两个依赖在pom.xml中 通过@EnableEurekaServer注解启动一个服务注册中心 在默认情况下该服务注册中心会将自己作为客户端来尝试注册它自己,所以我们需要禁用它的客户端行为,只需在application.properties中做如下配置: # 指定服务的端口号s

springcloud微服务系列之服务注册与发现组件Eureka

一.Eurake的简介二.使用Eureka进行服务的注册消费1.创建一个服务注册中心2.创建服务的提供者3.创建服务的消费者总结 一.Eurake的简介 今天我们来介绍下springcloud的核心组件Eureka,Eurake是负责微服务架构中服务治理的功能,负责各个服务实例的注册与发现. Eureka包含了服务器端和客户端组件.服务器端,也被称作是服务注册中心,用于提供服务的注册与发现. 客户端组件包含服务消费者与服务生产者.在应用程序运行时,服务生产者向注册中心注册自己的服务实例,当消费者

微服务~Eureka实现的服务注册与发现及服务之间的调用

微服务里一个重要的概念就是服务注册与发现技术,当你有一个新的服务运行后,我们的服务中心可以感知你,然后把加添加到服务列表里,然后当你死掉后,会从服务中心把你移除,而你作为一个服务,对其它服务公开的只是服务名称,而不是最终的服务地址URL,这对于云平台,容器化架构来说是非常重要的! 安装单独的Eureka服务(server) 服务注册-aspnetcore建立Eureka客户端(client) 服务发现-实现服务与服务的调用 一 安装单独的Eureka服务 安装tomcat,到apache官网ht

Spring Cloud 系列之 Eureka 实现服务注册与发现

如果你对 Spring Cloud 体系还不是很了解,可以先读一下 Spring Cloud 都有哪些模块 Eureka 是 Netflix 开源的服务注册发现组件,服务发现可以说是微服务架构的核心功能了,微服务部署之后,一定要有服务注册和发现的能力,Eureka 就是担任这个角色的.如果你用过 dubbo 的话,那一定知道 dubbo 中服务注册和发现的功能是用 zookeeper 来实现的. Eureka 目前是 2.x 版本,并且官方已经宣布不再维护更新.不过其实 Eureka 已经很稳定

白话SpringCloud | 第二章:服务注册与发现(Eureka)-上

前言 从本章节开始,正式进入SpringCloud的基础教程.从第一章<什么是SpringCloud>中我们可以知道,一个微服务框架覆盖的东西是很多的,而如何去管理这些服务或者说API接口,就显得异常重要了.所以本章节,主要介绍下SpringCloud中使用Eureka实现服务的注册与发现. 服务治理 Eureka实践 Eureka简单介绍 创建Eureka服务端 创建Eureka客户端 Eureka自我保护模式 参考资料 总结 最后 老生常谈 服务治理 服务治理是微服务架构中最为核心和基础的

Spring Cloud Eureka 分布式开发之服务注册中心、负载均衡、声明式服务调用实现

介绍 本示例主要介绍 Spring Cloud 系列中的 Eureka,使你能快速上手负载均衡.声明式服务.服务注册中心等 Eureka Server Eureka 是 Netflix 的子模块,它是一个基于 REST 的服务,用于定位服务,以实现云端中间层服务发现和故障转移. 服务注册和发现对于微服务架构而言,是非常重要的.有了服务发现和注册,只需要使用服务的标识符就可以访问到服务,而不需要修改服务调用的配置文件.该功能类似于 Dubbo 的注册中心,比如 Zookeeper. Eureka

Spring Cloud中Eureka的服务注册

Eureka中的服务注册,分为服务注册中心,和服务注册这. 一,服务注册者 服务注册者在启动的时候会通过发送rest请求的方式将自己注册到Eureka Server上.Eureka Server将服务注册的原信息保存在双层结构的map上,第一层的key是服务名,第二层的key是具体服务实例名. 在注册完成后服务提供者会维护一个心跳用来持续的,调用续约服务的时间间隔通过eureka.Instance.less-renewal-interval-in-seconds来设置,默认是30秒. 要开启服务

spring cloud(二)服务(注册)中心Eureka

Eureka是Netflix开源的一款提供服务注册和发现的产品,它提供了完整的Service Registry和Service Discovery实现.也是springcloud体系中最重要最核心的组件之一. 背景介绍 服务中心 服务中心又称注册中心,管理各种服务功能包括服务的注册.发现.熔断.负载.降级等,比如dubbo admin后台的各种功能. 有了服务中心调用关系会有什么变化,画几个简图来帮忙理解 项目A调用项目B 正常调用项目A请求项目B 有了服务中心之后,任何一个服务都不能直接去掉用

没使用Spring Cloud的版本管理导致Eureka服务无法注册到Eureka服务注册中心

创建了一个Eureka Server的服务注册集群(两个Eureka服务),都能相互注册,写了一个Eureka客户端服务无法注册到服务发现注册中心 注册中心1: 注册中心2: 服务正常: pom依赖文件: <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://