eureka源码分析

源码分析:

先从注解开始入手

1、org.springframework.cloud.client.discovery.EnableDiscoveryClient

@EnableDiscoveryClient注解 (将应用注册到Eureka Server或从Eureka Server中获取服务列表)

作用:用来开启DiscoveryClient的实例

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

}

 

2、接口 DiscoveryClient (org.springframework.cloud.client.discovery.DiscoveryClient)

作用:它定义了用来发现服务的常用抽象方法,通过该接口可以有效的屏蔽服务治理的实现细节。

 

package org.springframework.cloud.client.discovery;

/**

 * Represents read operations commonly available to discovery services such as Netflix Eureka or consul.io.

 */

public interface DiscoveryClient extends Ordered {

   ......

}

 

它的实现类 :

org.springframework.cloud.netflix.eureka.EurekaDiscoveryClient

org.springframework.cloud.kubernetes.discovery.KubernetesDiscoveryClient

org.springframework.cloud.client.discovery.simple.SimpleDiscoveryClient

org.springframework.cloud.client.discovery.composite.CompositeDiscoveryClient

 

3、EurekaDiscoveryClient

package org.springframework.cloud.netflix.eureka;

 

import com.netflix.discovery.EurekaClient;

import com.netflix.discovery.EurekaClientConfig;

 

 

public class EurekaDiscoveryClient implements DiscoveryClient {

    public static final String DESCRIPTION = "Spring Cloud Eureka Discovery Client";

    private final EurekaClient eurekaClient;

    private final EurekaClientConfig clientConfig;

    ......

    ......

    public List<ServiceInstance> getInstances(String serviceId) {....}

    public List<String> getServices() {......}

}

通过看其源码,EurekaDiscoveryClient都是通过调用底层netflix包中的类来实现功能,下面看对应类的源码

 

4、EurekaClient  (com.netflix.discovery.EurekaClient)

package com.netflix.discovery;

/**

 * Define a simple interface over the current DiscoveryClient implementation.

 * EurekaClient API contracts are:

 *  - provide the ability to get InstanceInfo(s) (in various different ways)

 *  - provide the ability to get data about the local Client (known regions, own AZ etc)

 *  - provide the ability to register and access the healthcheck handler for the client

 */

@ImplementedBy(DiscoveryClient.class)

public interface EurekaClient extends LookupService {

}

既然看到了这,当然接着看 DiscoveryClient了

 

5、DiscoveryClient (com.netflix.discovery.DiscoveryClient)(也可以叫 Eureka Client )

作用:这个类用于和Eureka Server相互协调,主要功能包括:

向Eureka Server注册服务实例

向Eureka Server服务租约

当服务关闭期间,向Eureka Server取消服务

查询Eureka Server中的服务实例列表

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

 

5.1  获取服务列表:

/**

 * @deprecated see replacement in {@link com.netflix.discovery.endpoint.EndpointUtils}

 *

 * Get the list of all eureka service urls from properties file for the eureka client to talk to.

 *

 * @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);

}

接着看:EndpointUtils.getServiceUrlsFromConfig

分析:客户端依次加载两个内容:Region 和 Zone

Region : 只有一个region,即通过getRegion函数从配置文件中读取一个Region返回。所以一个微服务应用只可以属于一个Region, 默认为default, 另外可以通过eureka.client.region设置

Zone    : 可以多个zone,  即通过getAvailabilityZones函数返回的集合。默认是defaultZone, 可以通过eureka.client.availability-zones属性设置.    

serviceUrls : 获取了Region和Zone后,才真正的加载Eureka Server的具体地址。getEurekaServerServiceUrls函数,根据传入的参数按照一定的算法确定加载位于哪个Zone配置的serviceUrls

/**

 * Get the list of all eureka service urls from properties file for the eureka client to talk to.

 */

public static List<String> getServiceUrlsFromConfig(EurekaClientConfig clientConfig, String instanceZone, boolean preferSameZone) {

    List<String> orderedUrls = new ArrayList<String>();

    String region = getRegion(clientConfig);

    String[] availZones = clientConfig.getAvailabilityZones(clientConfig.getRegion());

    if (availZones == null || availZones.length == 0) {

        availZones = new String[1];

        availZones[0] = DEFAULT_ZONE;

    }

   

    // 按照一定的算法确定加使用哪个zone

    int myZoneOffset = getZoneOffset(instanceZone, preferSameZone, availZones);

 

    List<String> serviceUrls = clientConfig.getEurekaServerServiceUrls(availZones[myZoneOffset]);

    ........

    ........

    return orderedUrls;

}

 

通过看clientConfig.getEurekaServerServiceUrls函数,就可以知道,eureka.client.serviceUrl.defaultZone属性可以配置多个,并且已逗号分割

补充说明:clientConfig采用实现类应该是 DefaultEurekaClientConfig,可以返现它的getEurekaServerServiceUrls函数对配置的defaultZone进行了按照逗号分割,但是并没有对每个值进行加 / 的处理

 

5.2、服务注册

通过 initScheduledTask方法

 

/**

 * Initializes all scheduled tasks.

 */

private void initScheduledTasks() {

    if (clientConfig.shouldRegisterWithEureka()) {

        int renewalIntervalInSecs = instanceInfo.getLeaseInfo().getRenewalIntervalInSecs();

        int expBackOffBound = clientConfig.getHeartbeatExecutorExponentialBackOffBound();

        logger.info("Starting heartbeat executor: " "renew interval is: {}", renewalIntervalInSecs);

        ......

        // InstanceInfo replicator

        instanceInfoReplicator = new InstanceInfoReplicator(

                this,

                instanceInfo,

                clientConfig.getInstanceInfoReplicationIntervalSeconds(),

                2); // burstSize

         ......

         ......

        instanceInfoReplicator.start(clientConfig.getInitialInstanceInfoReplicationIntervalSeconds());

    else {

        logger.info("Not registering with Eureka server per configuration");

    }

}

然后看 InstanceInfoReplicator 中的run方法:

package com.netflix.discovery;

/**

 * A task for updating and replicating the local instanceinfo to the remote server. Properties of this task are:

 * - configured with a single update thread to guarantee sequential update to the remote server

 * - update tasks can be scheduled on-demand via onDemandUpdate()

 * - task processing is rate limited by burstSize

 * - a new update task is always scheduled automatically after an earlier update task. However if an on-demand task

 *   is started, the scheduled automatic update task is discarded (and a new one will be scheduled after the new

 *   on-demand update).

 *

 *   @author dliu

 */

class InstanceInfoReplicator implements Runnable {

    public void run() {

    try {

        discoveryClient.refreshInstanceInfo();

 

        Long dirtyTimestamp = instanceInfo.isDirtyWithTime();

        if (dirtyTimestamp != null) {

            discoveryClient.register();

            instanceInfo.unsetIsDirty(dirtyTimestamp);

        }

    catch (Throwable t) {

        logger.warn("There was a problem with the instance info replicator", t);

    finally {

        Future next = scheduler.schedule(this, replicationIntervalSeconds, TimeUnit.SECONDS);

        scheduledPeriodicRef.set(next);

    }

}

}

 

我们看到了 discoveryClient.register();   ok, 看看它是如何注册的

/**

 * Register with the eureka service by making the appropriate REST call.

 */

boolean register() throws Throwable {

    logger.info(PREFIX + "{}: registering service...", appPathIdentifier);

    EurekaHttpResponse<Void> httpResponse;

    try {

        httpResponse = eurekaTransport.registrationClient.register(instanceInfo);

    catch (Exception e) {

        logger.warn(PREFIX + "{} - registration failed {}", appPathIdentifier, e.getMessage(), e);

        throw e;

    }

    if (logger.isInfoEnabled()) {

        logger.info(PREFIX + "{} - registration status: {}", appPathIdentifier, httpResponse.getStatusCode());

    }

    return httpResponse.getStatusCode() == Status.NO_CONTENT.getStatusCode();

}

 

通过命名可以猜出,注册操作是通过REST请求的方式进行的。同时,发起注册请求的时候,传入的对象是com.netflix.appinfo.InstanceInfo对象,也就是该对象就是注册时,客户端给服务端的服务元数据。

 

5.3 服务获取与服务续约

服务获取、服务续约、以及刚才分析的服务注册都在方法 initScheduledTask中

initScheduledTask有两个if判断:

// 服务获取

if (clientConfig.shouldFetchRegistry()) {。。。}

// 服务注册 + 服务续约

if (clientConfig.shouldRegisterWithEureka()) {。。。}

上面已经分析了下面的if,  下面在分析上面这个if(服务获取)

 

5.3.1. 服务获取

private void initScheduledTasks() {

    if (clientConfig.shouldFetchRegistry()) { // 对应配置文件的属性 eureka.client.fetch-registry=true,默认为true

        // registry cache refresh timer(注册缓冲的刷新)

        // 对应配置文件的eureka.client.registry-fetch-interval-seconds=30

        int registryFetchIntervalSeconds = clientConfig.getRegistryFetchIntervalSeconds();

        int expBackOffBound = clientConfig.getCacheRefreshExecutorExponentialBackOffBound();

        scheduler.schedule(

                new TimedSupervisorTask(

                        "cacheRefresh",

                        scheduler,

                        cacheRefreshExecutor,

                        registryFetchIntervalSeconds,

                        TimeUnit.SECONDS,

                        expBackOffBound,

                        new CacheRefreshThread()

                ),

                registryFetchIntervalSeconds, TimeUnit.SECONDS);

    }

    .......

    .......

}

 

服务获取:为了定期的更新客户端的服务清单,以保证客户端能够访问到健康的服务实例,『服务获取』的请求不仅只在服务启动的时候获取,而是有个定时任务

服务获取会根据是否是第一次获取发起不同的REST请求和相应的处理。(具体怎样的区别没看)

 

5.3.2 服务续约

(客户端周期性的 发送心跳 来更新它的服务租约)

private void initScheduledTasks() {

    if (clientConfig.shouldRegisterWithEureka()) {

        int renewalIntervalInSecs = instanceInfo.getLeaseInfo().getRenewalIntervalInSecs();

        int expBackOffBound = clientConfig.getHeartbeatExecutorExponentialBackOffBound();

        logger.info("Starting heartbeat executor: " "renew interval is: {}", renewalIntervalInSecs);

 

        // Heartbeat timer

        scheduler.schedule(

                new TimedSupervisorTask(

                        "heartbeat",

                        scheduler,

                        heartbeatExecutor,

                        renewalIntervalInSecs,

                        TimeUnit.SECONDS,

                        expBackOffBound,

                        new HeartbeatThread()

                ),

                renewalIntervalInSecs, TimeUnit.SECONDS);

   }

}

可以看到这有个心跳检测new HeartbeatThread(), 进入看看它都干了啥

/**

 * The heartbeat task that renews the lease in the given intervals.

 */

private class HeartbeatThread implements Runnable {

 

    public void run() {

        if (renew()) {

            lastSuccessfulHeartbeatTimestamp = System.currentTimeMillis();

        }

    }

}

 

----------------------------------------------------------------------------

/**

 * Renew with the eureka service by making the appropriate REST call

 */

boolean renew() {

    EurekaHttpResponse<InstanceInfo> httpResponse;

    try {

        httpResponse = eurekaTransport.registrationClient.sendHeartBeat(instanceInfo.getAppName(), instanceInfo.getId(), instanceInfo, null);

        .......

        .......

        }

        return httpResponse.getStatusCode() == Status.OK.getStatusCode();

    catch (Throwable e) {

        logger.error(PREFIX + "{} - was unable to send heartbeat!", appPathIdentifier, e);

        return false;

    }

}

 

Eureka客户端通过REST请求的方式与服务端进行交互,那么服务端是如何处理这些REST请求的呢?看6

6、 服务注册中心的处理

6.1 响应『服务注册』请求

服务端对请求进行一系列的校验后,调用方法进行注册。

调用com.netflix.eureka.registry.AbstractInstanceRegistry的register方法进行注册:registry.register(info, "true".equals(isReplication));

将InstanceInfo中的元数据信息存储在一个ConcurrentHashMap对象中。正如前面说到的注册中心存储了两层Map接口,第一层是key存储服务名,InstanceInfo中的appName属性,第二层的key存储实例名,对应InstanceInfo中的instanceId属性。

 

然后,将该新服务注册的信息广播出去

replicateToPeers(Action.Register, info.getAppName(), info.getId(), info, null, isReplication);

/**

 * Registers a new instance with a given duration.

 *

 * @see com.netflix.eureka.lease.LeaseManager#register(java.lang.Object, int, boolean)

 */

public void register(InstanceInfo registrant, int leaseDuration, boolean isReplication) {

    try {

        read.lock();

        Map<String, Lease<InstanceInfo>> gMap = registry.get(registrant.getAppName());

        REGISTER.increment(isReplication);

        if (gMap == null) {

            final ConcurrentHashMap<String, Lease<InstanceInfo>> gNewMap = new ConcurrentHashMap<String, Lease<InstanceInfo>>();

            // 双层map

            gMap = registry.putIfAbsent(registrant.getAppName(), gNewMap);

            if (gMap == null) {

                gMap = gNewMap;

            }

        }

....

....

}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值