springcloud系列—eureka—第1章-3: eureka详解

目录

eureka详解

基础架构:

服务治理机制:

服务提供者:

服务注册

服务同步

服务续约-心跳机制

服务消费者:

获取服务

服务调用

服务下线

服务注册中心:

失效剔除机制

自我保护

源码简单理解:

配置详解:

服务注册类配置:


eureka详解

前面两节,我们使用了服务注册中心、服务消费者、服务提供者,构建了eureka服务治理体系。因此对eureka也有了一定的了解,并且在使用服务消费者时接触了客户端的负载均衡ribbon的简单的接触。但是其实在实际项目中,这些东西的使用是很复杂的,所以我们还需要对eureka更透彻的理解,才能做出一些更符合项目实情的配置。下面我们将详细去理解eureka的基础架构、节点间的通信机制已经一些进阶的配置。

基础架构:

在前面的一章"服务治理"一节中,所使用的势力虽然小,但是包含了三个核心要素。

  1. 服务注册中心:eureka提供的服务端,提供服务注册与发现的能力。
  2. 服务提供者:提供接口服务的应用,他将自己提供的服务注册到服务注册中心,已供其他应用发现。
  3. 服务消费者:消费者从服务注册中心去获取服务列表,从而消费者知道去何处调用自己所需的服务,在上一节中我们就使用了ribbon来实现服务消费,以后还会介绍使用feign的消费方式。

服务治理机制:

在体验了eureka通过简单的注解配置就是实现了强大的服务治理功能,我们来了解下eureka基础架构中各个元素的一些通信行为,以此来理解eureka实现的服务治理体系是如何运作的。几个重要元素先理解一下:

  • “服务注册中心-1” 和 “服务注册中心-2” ,两者之间互相注册,相差了高可用集群的注册中心
  • “服务提供者例”启动两个实例,这两个实例分别只注册到一个服务注册中心。
  • 启动两个“服务消费者”实例,也一样分别只注册到一个注册中心

 根据上面的结构,我们可以来仔细了解一下各个节点之间的一些重要通信行为。

服务提供者:

  • 服务注册

“服务提供者”在启动的时候会通过发送rest请求的方式将自己注册到eureka server服务注册中心,同时带上了自身的一些元数据。注册中心接收到这个rest请求之后,将元数据信息存储在一个双层结构Map中,其中第一层的key是服务名,第二层的key是具体服务的实例名。

结构:(“key(服务名)”:(“key具体服务的实例名”:“元数据”))

spring.application.name=服务名

eureka.instance.hostname=服务实例名

在注册时,需要确认一下配置 eureka.client.register-with-eureka=true参数是否为true。若设置为false则不会启动注册操作。sss

  • 服务同步

如上面的架构图中所示,两个服务注册中心之间互相注册。而两个服务提供者分别只注册一个注册中心。也就是说这两个服务提供者的信息分别被服务服务注册中心所维护。因为注册中心是相互注册为服务,当服务提供者注册请求到一个服务注册中心时,它会将请求转发给集群中相连的其他注册中心,从而实现注册中心之间的服务同步。通过服务同步,服务提供者的服务信息就能被任何一台注册中心调用到。

  • 服务续约-心跳机制

在注册完服务之后,服务提供者会维护一个心跳来持续告诉eureka server注册中心,表明自己还活着,以防注册中心的“剔除任务”将自己从服务列表中剔除出去。我们称该机制为服务续约。

关于服务续约有两个重要的属性:

eureka.instance.lease-renewal-interval-in-seconds=30;该参数用于定义服务续约任务的调用时间间隔,默认为30秒。

eureka.instance.lease-expiration-duration-in-seconds=90;该参数用于定义服务失效的时间,默认为90秒。

服务消费者:

  • 获取服务

目前了解为止,我们已经理解了服务注册中心和服务提供者。而当我们启动服务消费者时,它会发送一个rest请求给服务注册中心,来获取注册中心上面的服务列表清单。为了性能考虑,eureka server注册中心会维护一份只读的服务清单来返回给客户端,同事缓存清单每隔30秒更新一次。

获取服务是服务消费者的基础,所以必须确保eureka.client.fetch-registry=true参数没有被修改成false,该值默认为true。若希望修改缓存清单的更新时间,可以通过eureka.client.registry-fetch-interval-seconds=30参数进行修改,该参数默认为30,单位为秒。

  • 服务调用

服务调用者在获取服务清单后,通过服务名可以获取具体提供的实例名和该实例的元数据信息。因为有这些服务实例的详细信息,所以客户端可以根据自己的需求决定具体调用哪个实例,在ribbon中会默认采用轮询的方式进行调用,从而实现客户端的负载均衡。

对于访问实例的选择,eureka中有region和zone的概念,一个region中可以包含多个zone,每个服务客户端需要被注册到一个zone中,所以每个客户端对应一个region和一个zone。在进行服务调用的时候,优先访问同处一个zone中的服务提供方,若访问不到,就访问其他的zone。

  • 服务下线

在系统运行过程中必然会面临关闭或重启服务的某个实例的情况,在服务关闭期间,我们自然不希望客户端会继续调用到被关闭的实例。所以在客户端程序中,当服务实例进行正常的关闭操作时,它会触发一个服务下线的rest请求给eureka server注册中心,告诉服务注册中心:“我要下线了”。服务端在接收到请求之后,将该服务状态置为下线(down),并把该下线事件传播出去。然后注册中心之间服务同步就会都知道该服务客户端已下线。

服务注册中心:

  • 失效剔除机制

有些时候我们的服务实例并不一定是正常下线,有可能是内存溢出、网络故障等问题使得服务不能正常工作,而服务注册中心没有收到“服务下线”的请求。为了从服务列表中将这些无法提供服务的实例剔除掉,eureka server注册中心在启动的时候会创建一个定时任务,默认每隔一段时间(60秒)将当前清单中超时(默认为90秒)没有续约的服务剔除出去。

  • 自我保护

当我们在本地调试eureka时,基本上会碰到这样一i个情况,在服务注册中心的信息面板中出现红色警告:

实际上,该警告就是出发了自我保护机制。之前说过,服务注册到eureka server注册中心之后,会维护一个心跳机制,来告诉eureka server自己还活着。eureka server 注册中心在运行期间,会统计心跳失败的比例在15分钟之内是否低于85%,如果出现低于的情况,eureka server会将当前的实例注册信息保护起来,让这些实例不会过期,经可能的保护这些信息。但是,在这段保护期间若实例出现问题,那么客户端很容易拿到实际已经不存在的服务实例,会出现调试失败的情况,所有客户端必须要有容错机制。比如可以使用请求重试、断路器等机制。

由于本地调试很容易就出发了注册中心的保护机制,这会使得注册中心维护的服务实例不那么准确。所以我们在本地开发的时候,可以使用

eureka. server.enable-self-oreservation=false;关闭保护机制,确保将不可用的实例剔除。

源码简单理解:

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import({EnableDiscoveryClientImportSelector.class})
public @interface EnableDiscoveryClient {
    boolean autoRegister() default true;
}

public static List<String> getServiceUrlsFromConfig(EurekaClientConfig clientConfig, String instanceZone, boolean preferSameZone) {
        List<String> orderedUrls = new ArrayList();
        String region = getRegion(clientConfig);
        String[] availZones = clientConfig.getAvailabilityZones(clientConfig.getRegion());
        if (availZones == null || availZones.length == 0) {
            availZones = new String[]{"default"};
        }

        logger.debug("The availability zone for the given region {} are {}", region, availZones);
        int myZoneOffset = getZoneOffset(instanceZone, preferSameZone, availZones);
        List<String> serviceUrls = clientConfig.getEurekaServerServiceUrls(availZones[myZoneOffset]);
        if (serviceUrls != null) {
            orderedUrls.addAll(serviceUrls);
        }

        int currentOffset = myZoneOffset == availZones.length - 1 ? 0 : myZoneOffset + 1;

        while(currentOffset != myZoneOffset) {
            serviceUrls = clientConfig.getEurekaServerServiceUrls(availZones[currentOffset]);
            if (serviceUrls != null) {
                orderedUrls.addAll(serviceUrls);
            }

            if (currentOffset == availZones.length - 1) {
                currentOffset = 0;
            } else {
                ++currentOffset;
            }
        }

        if (orderedUrls.size() < 1) {
            throw new IllegalArgumentException("DiscoveryClient: invalid serviceUrl specified!");
        } else {
            return orderedUrls;
        }
    }

    public static String getRegion(EurekaClientConfig clientConfig) {
        String region = clientConfig.getRegion();
        if (region == null) {
            region = "default";
        }

        region = region.trim().toLowerCase();
        return region;
    }

public String[] getAvailabilityZones(String region) {
        String value = (String)this.availabilityZones.get(region);
        if (value == null) {
            value = "defaultZone";
        }

        return value.split(",");
    }

public List<String> getEurekaServerServiceUrls(String myZone) {
        String serviceUrls = (String)this.serviceUrl.get(myZone);
        if (serviceUrls == null || serviceUrls.isEmpty()) {
            serviceUrls = (String)this.serviceUrl.get("defaultZone");
        }

        if (!StringUtils.isEmpty(serviceUrls)) {
            String[] serviceUrlsSplit = StringUtils.commaDelimitedListToStringArray(serviceUrls);
            List<String> eurekaServiceUrls = new ArrayList(serviceUrlsSplit.length);
            String[] var5 = serviceUrlsSplit;
            int var6 = serviceUrlsSplit.length;

            for(int var7 = 0; var7 < var6; ++var7) {
                String eurekaServiceUrl = var5[var7];
                if (!this.endsWithSlash(eurekaServiceUrl)) {
                    eurekaServiceUrl = eurekaServiceUrl + "/";
                }

                eurekaServiceUrls.add(eurekaServiceUrl);
            }

            return eurekaServiceUrls;
        } else {
            return new ArrayList();
        }
    }

配置详解:

服务注册类配置:

猜你喜欢

转载自blog.csdn.net/weixin_40663800/article/details/82749464