Eureka微服务云架构源码分析

在看具体源码前,咱们先回顾一下以前咱们所实现的内容,从而找一个合适的切入口去分析。首先,服务注册中心、服务提供者、服务消费者这三个主要元素来讲,后二者(也就是Eureka客户端)在整个运行机制中是大部分通讯行为的主动发起者,而注册中心主要是处理请求的接收者。因此,咱们能够从Eureka的客户端做为入口看看它是如何完成这些主动通讯行为的。算法

咱们在将一个普通的Spring Boot应用注册到Eureka Server中,或是从Eureka Server中获取服务列表时,主要就作了两件事:spring

  • 在应用主类中配置了@EnableDiscoveryClient注解
  • application.properties中用eureka.client.serviceUrl.defaultZone参数指定了服务注册中心的位置

顺着上面的线索,咱们先查看@EnableDiscoveryClient的源码以下:架构

/**
 * Annotation to enable a DiscoveryClient implementation.
 * @author Spencer Gibb
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(EnableDiscoveryClientImportSelector.class)
public @interface EnableDiscoveryClient {

}

从该注解的注释咱们能够知道:该注解用来开启DiscoveryClient的实例。经过搜索DiscoveryClient,咱们能够发现有一个类和一个接口。经过梳理能够获得以下图的关系:app

其中,左边的org.springframework.cloud.client.discovery.DiscoveryClient是Spring Cloud的接口,它定义了用来发现服务的经常使用抽象方法,而org.springframework.cloud.netflix.eureka.EurekaDiscoveryClient是对该接口的实现,从命名来就能够判断,它实现的是对Eureka发现服务的封装。因此EurekaDiscoveryClient依赖了Eureka的com.netflix.discovery.EurekaClient接口,EurekaClient继承了LookupService接口,他们都是Netflix开源包中的内容,它主要定义了针对Eureka的发现服务的抽象方法,而真正实现发现服务的则是Netflix包中的com.netflix.discovery.DiscoveryClient类。框架

那么,咱们就看看来详细看看DiscoveryClient类。先解读一下该类头部的注释有个整体的了解,注释的大体内容以下:函数

这个类用于帮助与Eureka Server互相协做。

Eureka Client负责了下面的任务:
- 向Eureka Server注册服务实例
- 向Eureka Server为租约续期
- 当服务关闭期间,向Eureka Server取消租约
- 查询Eureka Server中的服务实例列表

Eureka Client还须要配置一个Eureka Server的URL列表。

在具体研究Eureka Client具体负责的任务以前,咱们先看看对Eureka Server的URL列表配置在哪里。根据咱们配置的属性名:eureka.client.serviceUrl.defaultZone,经过serviceUrl咱们找到该属性相关的加载属性,可是在SR5版本中它们都被@Deprecated标注了,并在注视中能够看到@link到了替代类com.netflix.discovery.endpoint.EndpointUtils,咱们能够在该类中找到下面这个函数:
 微服务

public static Map<String, List<String>> getServiceUrlsMapFromConfig(
			EurekaClientConfig clientConfig, String instanceZone, boolean preferSameZone) {
    Map<String, List<String>> orderedUrls = new LinkedHashMap<>();
    String region = getRegion(clientConfig);
    String[] availZones = clientConfig.getAvailabilityZones(clientConfig.getRegion());
    if (availZones == null || availZones.length == 0) {
        availZones = new String[1];
        availZones[0] = DEFAULT_ZONE;
    }
	……
    int myZoneOffset = getZoneOffset(instanceZone, preferSameZone, availZones);

    String zone = availZones[myZoneOffset];
    List<String> serviceUrls = clientConfig.getEurekaServerServiceUrls(zone);
    if (serviceUrls != null) {
        orderedUrls.put(zone, serviceUrls);
    }
	……
    return orderedUrls;
}

Region、Zone

在上面的函数中,咱们能够发现客户端依次加载了两个内容,第一个是Region,第二个是Zone,从其加载逻上咱们能够判断他们之间的关系:this

  • 经过getRegion函数,咱们能够看到它从配置中读取了一个Region返回,因此一个微服务应用只能够属于一个Region,若是不特别配置,就默认为default。若咱们要本身设置,能够经过eureka.client.region属性来定义。
  • public static String getRegion(EurekaClientConfig clientConfig) {
        String region = clientConfig.getRegion();
        if (region == null) {
            region = DEFAULT_REGION;
        }
        region = region.trim().toLowerCase();
        return region;
    }

    经过getAvailabilityZones函数,咱们能够知道当咱们没有特别为Region配置Zone的时候,将默认采用defaultZone,这也是咱们以前配置参数eureka.client.serviceUrl.defaultZone的由来。若要为应用指定Zone,咱们能够经过eureka.client.availability-zones属性来进行设置。从该函数的return内容,咱们能够Zone是能够有多个的,而且经过逗号分隔来配置。由此,咱们能够判断Region与Zone是一对多的关系。spa

  • public String[] getAvailabilityZones(String region) {
    	String value = this.availabilityZones.get(region);
    	if (value == null) {
    		value = DEFAULT_ZONE;
    	}
    	return value.split(",");
    }

    ServiceUrls

    在获取了Region和Zone信息以后,才开始真正加载Eureka Server的具体地址。它根据传入的参数按必定算法肯定加载位于哪个Zone配置的serviceUrls。.net

  • int myZoneOffset = getZoneOffset(instanceZone, preferSameZone, availZones);
    String zone = availZones[myZoneOffset];
    List<String> serviceUrls = clientConfig.getEurekaServerServiceUrls(zone);

    具体获取serviceUrls的实现,咱们能够详细查看getEurekaServerServiceUrls函数的具体实现类EurekaClientConfigBean,该类是EurekaClientConfigEurekaConstants接口的实现,用来加载配置文件中的内容,这里有很是多有用的信息,这里咱们先说一下此处咱们关心的,关于defaultZone的信息。经过搜索defaultZone,咱们能够很容易的找到下面这个函数,它具体实现了,如何解析该参数的过程,经过此内容,咱们就能够知道,eureka.client.serviceUrl.defaultZone属性能够配置多个,而且须要经过逗号分隔。

  • public List<String> getEurekaServerServiceUrls(String myZone) {
    	String serviceUrls = this.serviceUrl.get(myZone);
    	if (serviceUrls == null || serviceUrls.isEmpty()) {
    		serviceUrls = this.serviceUrl.get(DEFAULT_ZONE);
    	}
    	if (!StringUtils.isEmpty(serviceUrls)) {
    		final String[] serviceUrlsSplit = StringUtils.commaDelimitedListToStringArray(serviceUrls);
    		List<String> eurekaServiceUrls = new ArrayList<>(serviceUrlsSplit.length);
    		for (String eurekaServiceUrl : serviceUrlsSplit) {
    			if (!endsWithSlash(eurekaServiceUrl)) {
    				eurekaServiceUrl += "/";
    			}
    			eurekaServiceUrls.add(eurekaServiceUrl);
    		}
    		return eurekaServiceUrls;
    	}
    	return new ArrayList<>();
    }

    当客户端在服务列表中选择实例进行访问时,对于Zone和Region遵循这样的规则:优先访问同本身一个Zone中的实例,其次才访问其余Zone中的实例。经过Region和Zone的两层级别定义,配合实际部署的物理结构,咱们就能够有效的设计出区域性故障的容错集群。

    从如今开始,我这边会将近期研发的springcloud微服务云架构的搭建过程和精髓记录下来,帮助更多有兴趣研发spring cloud框架的朋友,但愿能够帮助更多的好学者。你们来一块儿探讨spring cloud架构的搭建过程及如何运用于企业项目。

相关文章
相关标签/搜索