Eureka源码分析

前面对 Eureka 做了比较全面的说明,相信大部分读者都可以搭建 Eureka 框架和根据自己的需求从代码层面选择合适的配置项,同时也能够写一些服务进行开发。

上文也对 Eureka 基础框架、服务治理的机制做了说明,读者可以从中理解 Eureka 的运行原理。但是还不曾涉及代码层面,许多读者想理解源码,却无从下手,因此,在这里对 Eureka 的源码做一个分析,让读者了解服务端与客户端的通信。这里只说明重点,具体读者可以通过设置断点观察。

DiscoveryClient实例

在客户端的启动类上添加注解 @EnableEurekaClient,启动 DiscoveryClient 实例。在进行具体的说明前,我们先看 DiscoveryClient 的类关系图,如图10.18所示。

image 2024 04 01 08 29 54 308
Figure 1. 图10.18 DiscoveryClient关系图

DiscoveryClient 类是 Spring Cloud 提供的接口,用于定义服务发现的抽象发现,而实现类 EurekaDiscoveryClient 则是对接口的实现,即 Eureka 服务发现。而 EurekaDiscoveryClient 则依赖于 EurekaClient 接口。

DiscoveryClient 类也实现了 EurekaClient 接口,而且是 Netfix 对服务发现的一个实现。因此,真正用于服务发现的类是 DiscoveryClient。对这个类的说明如下。

* The class that is instrumental for interactions with <tt>Eureka Server</tt>.
*
* <p>
* <tt>Eureka Client</tt> is responsible for a) <em>Registering</em> the
* instance with <tt>Eureka Server</tt> b) <em>Renewal</em>of the lease with
* <tt>Eureka Server</tt> c) <em>Cancellation</em> of the lease from
* <tt>Eureka Server</tt> during shutdown
* <p>
* d) <em>Querying</em> the list of services/instances registered with
* <tt>Eureka Server</tt>
* <p>
*
* <p>
* <tt>Eureka Client</tt> needs a configured list of <tt>Eureka Server</tt>
* {@link java.net.URL}s to talk to.These {@link java.net.URL}s are typically amazon elastic eips
* which do not change. All of the functions defined above fail-over to other
* {@link java.net.URL}s specified in the list in the case of failure.
* </p>

上文主要说明了 DiscoveryClient 类的功能。它主要用于与 Eureka Server 互相协作。

Eureka Client 负责向 Server 注册服务实例;向 Server 租约续期;服务关闭期间,向 Server 取消租约;查询 Server 中的服务实例列表,Eureka Client 还需要配置一个 Eureka Server 的 URL 列表。

服务发现

根据 DiscoveryClient 的说明,我们需要进入这个类查找源码。首先,我们需要得到与 Eureka 客户端对话的所有 Eureka Server 的 URL 列表,通过此思路可以找到相应的代码,代码如下所示。

/**
  * @deprecated see replacement in {@link com.netflix.discovery. endpoint.EndpointUtils}
  * @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 The list of all eureka service urls for the eureka client to talk to
  */
@Deprecated
@Override
public List<String> getServiceUrlsFromConfig(String instanceZone, boolean preferSameZone) {
      return EndpointUtils.getServiceUrlsFromConfig(clientConfig, instanceZone, preferSameZone);
}

注意,在这里标注 @deprecated,参照 EndpointUtils。那么我们来看 EndpointUtils 的程序,部分代码如下所示。

public static Map<String, List<String>> getServiceUrlsMapFrom Config(EurekaClientConfig clientConfig, String instanceZone, boolean preferSameZone) {
   Map<String, List<String>> orderedUrls = new LinkedHashMap<>();
   String region = getRegion(clientConfig);
   String[] availZones = clientConfig.getAvailabilityZones(clientCo nfig.getRegion());
   if (availZones == null || availZones.length == 0) {
      availZones = new String[1];
      availZones[0] = DEFAULT_ZONE;
   }
   //省略一部分代码
   int currentOffset = myZoneOffset == (availZones.length - 1) ? 0 :(myZoneOffset + 1);
   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++;
      }
   }
//省略一部分代码
   return orderedUrls;
}

上面的加粗部分是重点。在这里客户端主要加载两个部分,一个为 region,另一个为 zone。getRegion 函数在这里就不讲了,主要功能是读取 region 并返回,如果不配置则默认为 default。每个微服务对应一个 region。getAvailabilityZones 函数在这里也不讲解了,只说说这里的功能,默认的 zone 为 defaultZone,如果设置了则会通过逗号进行分割。

最后,是 getEurekaServerServiceUrls 方法。这个方法会真正地加载 Server 的具体地址。关于该方法的源码如下所示。

public List<String> getEurekaServerServiceUrls(String myZone) {
   String serviceUrls = configInstance.getStringProperty(
  namespace + CONFIG_EUREKA_SERVER_SERVICE_URL_PREFIX + "." +myZone, null).get();
   if (serviceUrls == null || serviceUrls.isEmpty()) {
      serviceUrls = configInstance.getStringProperty(
            namespace + CONFIG_EUREKA_SERVER_SERVICE_URL_PREFIX+ ".default", null).get();
   }
   if (serviceUrls != null) {
      return Arrays.asList(serviceUrls.split(URL_SEPARATOR));
   }
   return new ArrayList<String>();
}