SpringEureka源码分析之Region与Zone

破局之路课程 2024-03-25 00:25:47
前言

用户量比较大或者用户地理位置分布范围很广的项目,一般都会有多个机房。这个时候如果上线springCloud服务的话,我们希望一个机房内的服务优先调用同一个机房内的服务,当同一个机房的服务不可用的时候,再去调用其它机房的服务,以达到减少延时的作用。 eureka中便出现了Region与Zone。

eureka提供了region和zone两个概念来进行分区,这两个概念均来自于亚马逊的AWS:

(1)region:可以简单理解为地理上的分区,比如亚洲地区,或者华北地区,再或者北京等等,没有具体大小的限制。根据项目具体的情况,可以自行合理划分region。

(2)zone:可以简单理解为region内的具体机房,比如说region划分为北京,然后北京有两个机房,就可以在此region之下划分出zone1,zone2两个zone。

分区服务的部署架构图

如上图所示,有一个region:beijing,下面有zone-1和zone-2两个分区,每个分区内有一个注册中心Eureka Server和一个服务提供者Service。我们在zone-1内创建一个Consumer-1服务消费者的话,其会优先调用同一个zone内的Service-1,当Service-1不可用时,才会去调用zone-2内的Service-2。

从源码角度看

首先看com.netflix.discovery.endpoint.EndpointUtils类下这个方法:

public static List<String> getServiceUrlsFromConfig(EurekaClientConfig clientConfig, String instanceZone, boolean preferSameZone) { List<String> orderedUrls = new ArrayList<String>(); // 拿到region String region = getRegion(clientConfig); // 拿到region下的zone String[] availZones = clientConfig.getAvailabilityZones(clientConfig.getRegion()); if (availZones == null || availZones.length == 0) { // 没有拿默认zone availZones = new String[1]; availZones[0] = DEFAULT_ZONE; } 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!"); } return orderedUrls; }

在上面的方法中,可以发现,客户端依次加载了两个内容,分别就是region和zone,从加载逻辑上可以看出region包含zone,也就是一对多的关系。

getRegion()方法:

public static String getRegion(EurekaClientConfig clientConfig) { String region = clientConfig.getRegion(); if (region == null) { // 不配置默认是 default region = DEFAULT_REGION; } region = region.trim().toLowerCase(); return region; }

getAvailabilityZones()方法:

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

可以看出

1、不配置默认是 default

2、设置多个zone用,分割。配置文件中用eureka.client.availability-zones属性设置。

// 在看这两行代码,在真正拿去eureka服务地址int myZoneOffset = getZoneOffset(instanceZone, preferSameZone, availZones);List<String> serviceUrls = clientConfig.getEurekaServerServiceUrls(availZones[myZoneOffset]);

跟踪getEurekaServerServiceUrls(availZones[myZoneOffset])方法,此方法具体实现了如何拿去服务地址

@Override 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.trim()); } return eurekaServiceUrls; } return new ArrayList<>(); }总结

当我们在微服务应用中使用Ribbon来实现服务调用时,对于zone的设置可以再负载均衡时实现区域亲和特性。Ribbon的默认策略会有限访问同客户端处于同一个zone中的服务端实例,只有当同一个zone中没有可用的服务实例的时候才会访问其他Zone的实例。所以通过zone属性的定义,配合实际部署的物理结构可以有效设计出对区域性故障的容错集群。

0 阅读:0