这部分源码涉及到两个类:
1. com.netflix.discovery.endpoint.EndpointUtils
2. org.springframework.cloud.netflix.eureka.EurekaClientConfigBean
我断点跟踪使用的客户端配置文件:
spring.application.name=hello-service
server.port=8001
eureka.client.region=shanghai
eureka.client.availability-zones.shanghai=theBund,disney
eureka.client.service-url.disney=http://peer1:1111/eureka/
eureka.client.service-url.theBund=http://peer2:1112/eureka/
解读开始:
/**
* Get the list of all eureka service urls from properties file for the eureka client to talk to.
*
* @param clientConfig the clientConfig to use
* @param instanceZone The zone in which the client resides
* @param preferSameZone true if we have to prefer the same zone as the client, false otherwise
* @return an (ordered) map of zone -> list of urls mappings, with the preferred zone first in iteration order
*/
public static Map<String, List<String>> getServiceUrlsMapFromConfig(EurekaClientConfig clientConfig, String instanceZone, boolean preferSameZone) {
Map<String, List<String>> orderedUrls = new LinkedHashMap<>();
String region = getRegion(clientConfig); // #1
String[] availZones = clientConfig.getAvailabilityZones(clientConfig.getRegion()); // #2
if (availZones == null || availZones.length == 0) {
availZones = new String[1];
availZones[0] = DEFAULT_ZONE;
}
logger.debug("The availability zone for the given region {} are {}", region, Arrays.toString(availZones));
int myZoneOffset = getZoneOffset(instanceZone, preferSameZone, availZones); // #3
String zone = availZones[myZoneOffset];
List<String> serviceUrls = clientConfig.getEurekaServerServiceUrls(zone); // #4
if (serviceUrls != null) {
orderedUrls.put(zone, serviceUrls);
}
int currentOffset = myZoneOffset == (availZones.length - 1) ? 0 : (myZoneOffset + 1); // #5
while (currentOffset != myZoneOffset) {
zone = availZones[currentOffset];
serviceUrls = clientConfig.getEurekaServerServiceUrls(zone);
if (serviceUrls != null) {
orderedUrls.put(zone, serviceUrls);
}
if (currentOffset == (availZones.length - 1)) {
currentOffset = 0;
} else {
currentOffset++;
}
}
if (orderedUrls.size() < 1) {
throw new IllegalArgumentException("DiscoveryClient: invalid serviceUrl specified!");
}
return orderedUrls;
}
根据类注释可以知道,这个工具类主要是用来从资源文件中获取所有eureka服务端的url,以便提供给eureka客户端访问使用;观察方法签名:
参数1是资源文件对象,参数2是客户端所在Zone(若有设置了多个,则选用第一个zone),参数3含义为是否偏好处于同一Zone的Eureka服务端(参数为eureka.client.prefer-same-zone-eureka,默认为true);
该方法的返回值就是客户端所维护的eureka服务注册中心的URL列表,是一个map集合,其中KEY为zone的名字,VALUE是元素为string的list,存储着key对应的serverUrls。
进入该方法后,根据上面的说明以及我的配置可知,参数1instanceZone是我配置的第一个Zone,即“theBund”,参数3preferSameZone的值为true。运行到#1处,getRegion()方法为从配置文件获取设置的region,代码很短,如下:
/**
* Get the region that this particular instance is in.
*
* @return - The region in which the particular instance belongs to.
*/
public static String getRegion(EurekaClientConfig clientConfig) {
String region = clientConfig.getRegion();
if (region == null) {
region = DEFAULT_REGION;
}
region = region.trim().toLowerCase();
return region;
}
通过上面代码,可以看到如果我不设置region,那么默认是DEFAULT_REGION(值为“default”),同时,根据返回值也可以知道一个微服务(eureka客户端)只能对应一个region。
在获取了region后,接着往下看,运行到#2处:
String[] availZones = clientConfig.getAvailabilityZones(clientConfig.getRegion())这行,这行代码的主要作用是从配置文件中根据上面获取的region获取可用的zone,getAvailabilityZones()的源码也很短,如下:
public String[] getAvailabilityZones(String region) {
String value = (String)this.availabilityZones.get(region);
if (value == null) {
value = "defaultZone";
}
return value.split(",");
}
其中,value的值为“theBund,disney”,对应的是配置文件中配置的eureka.client.service-url的值,返回值是一个list,下文中会用zoneList引用。
回过头来继续看EndpointUtils类,在#2处获取了region对应的zone(s)后,接下来惯例为空赋默认值,这里的默认值为DEFAULT_ZONE(值为“default”),注意,此处的默认值与getAvailabilityZones()方法给的缺省值(值为“defaultZone”)不同,不知道为啥用不同的值呵呵。
这样,region与对应的zone(s)都已经读取出来了,那么接下来我们就可以加载每个zone对应的serverUrl了。运行到#3处,这里的getZoneOffset()方法是获取在加载zoneList(上面获取的region对应的zone列表)的起始位置,这一段的源码很短,但是逻辑很有意思,所以也粘下来:
/**
* Gets the zone to pick up for this instance.
*/
private static int getZoneOffset(String myZone, boolean preferSameZone, String[] availZones) {
for (int i = 0; i < availZones.length; i++) {
if (myZone != null && (availZones[i].equalsIgnoreCase(myZone.trim()) == preferSameZone)) {
return i;
}
}
logger.warn("DISCOVERY: Could not pick a zone based on preferred zone settings. My zone - {}," +
" preferSameZone- {}. Defaulting to " + availZones[0], myZone, preferSameZone);
return 0;
}
至此,我们先将参数捋一下:
myZone:theBund
preferSameZone:true
availZones:{"theBund","disney"}
其中myZone是程序获取的第一个zone,肯定处于availZones的第一下标位置,因此,如果参数eureka.client.preferSameZone为true的话,那么返回的索引一定是0;如果为false,那么一定是1。
回过头来,由于我没有设置eureka.client.prefer-same-zone-eureka,缺省为true,所以返回0(赋值给myZoneOffset);接下来的代码含义就是,从myZoneOffset开始,读取availZones列表封装进Map中(即返回值map)。关键步骤#4就是根据zone值总配置文件中回去该zone对应的serverUrl,这段的源码没什么意思,但是也粘出来吧:
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();
}
}
我们找到判断空赋默认值的那行,如果传入的zone为null,那么程序会获取zone为“defaultZone”的serverUrl,然后我们根据返回值可以知道,同一个zone可以配置多个server。
获取到了zone对应的serverUrl后,此时我的serviceUrls(源码变量)是{"http://peer2:1112/eureka/"},接下来的代码是将zone作为key,将serviceUrls作为value装入返回值map中。OK,现在代码已经成功加载了第一个zone的serverUrl。
最后一步,下面的代码作用是加载剩余zone的serverUrl,这段代码逻辑比较好玩,可以看一下#5处这行代码:
int currentOffset=myZoneOffset == (availZones.length - 1)? 0 : (myZzoneOffset + 1)
这行代码就是从myZoneOffset下一个下标开始将zone的serverUrl放到返回值map中,而接下来的while循环则是从currentOffset开始依次遍历剩下的zone,依次获取每个zone对应的serverUrl,我们举个例子:
假设现在zoneList中还有3个zone,那么假如eureka.client.prefer-same-zone-eureka为true:
那么加载顺序是:
currentOffset:1 myZoneOffset:0
currentOffset:2 myZoneOffset:0
currentOffset:3 myZoneOffset:0
如果eureka.client.prefer-same-zone-eureka为false,那么加载顺序是:
currentOffset:2 myZoneOffset:1
currentOffset:3 myZoneOffset:1
currentOffset:0 myZoneOffset:1
这段如果不懂的话可以在纸上模拟写一下顺序哈,自己理解一下。
最后,返回orderedUrls,作为客户端加载的eureka服务端地址列表。
至此,完毕。
纯属自己分析,可能会有一些地方理解错误,真诚希望各路兄弟指出有误之处。