SpringCloud Eureka 源码分析

目录

  • 目录
  • SpringCloud-Eureka
    • 整合项目
      • Eureka架构图
        • 关键概念
          • Region区域
          • Zone可用区
          • 租约lease
          • Eureka Server
          • Application Service
          • Application Client
    • SpringCloud启动Eureka 的过程
      • EnableEurekaServer注解
      • SpringCloud与jersey Rest框架
      • jersey启动
      • 配置信息加载
      • EurekaClientAutoConfiguration InstanceInfo EurekaClient
        • EurekaClientConfigBean
        • DiscoveryClient 源码
      • EurekaServer初始化 EurekaServerAutoConfiguration
        • EurekaServerConfigBean
        • SpringCloud的InstanceRegistry与eureka的InstanceRegistry
        • EurekaServerContext
          • PeerEurekaNodes 与 PeerEurekaNode
        • EurekaServerBootstrap
    • 总结

SpringCloud-Eureka

整合项目

https://github.com/spring-cloud/spring-cloud-netflix

spring-cloud-netflix-eureka-server

spring-cloud-netflix-eureka-client

SpringCloud 集成Netflix的组件说明文档地址

Netflix的Wikihttps://github.com/Netflix/eureka/wiki

Eureka架构图

image

关键概念

Region(区域)
  • 属于AWS概念,主要为了给基础设施服务划分区域,实现高可用,提高网络传输速度。
Zone(可用区)
  • 属于AWS概念,为了实现高可用,在每个Region中可以有多个Zone,且每个Zone都是独立的,一个可用区出问题不会影响到其他可用区,这样也可以实现网络的低延迟。上图中的us-east-1c表示的就是一个可用区。
租约(lease)
  • Eureka Server 用于管理Erueka Client(主要管理Application Service)
  • 客户端通过每隔30秒,向Eureka Server发送心跳来续约,如果Eureka Server在90秒内没有收到客户端的续约,则会将该客户端从注册表里删除。
Eureka Server
  • 提供服务的注册和发现的功能
  • Register 提供服务注册功能
  • Renew 提供服务续租约(lease)功能
  • Cancel 提供服务注销功能
  • Get Registry 提供注册表获取功能,即服务发现
Application Service
  • 服务提供者。
Application Client
  • 服务消费者,每个client会缓存注册表的信息,这样可以再Eureka Server不可用的时候,不影响服务消费者同服务提供者的交互,同ZK的主要区别,即实现CAP中的AP。

SpringCloud启动Eureka 的过程:

EnableEurekaServer注解

SpringCloud通过注解EnableEurekaServer启动eureka服务,其包含了EnableDiscoveryClient。

SpringCloud与jersey Rest框架

eureka 基于jersey实现Rest服务,因此,如果不想采用jersey,则只需要过滤相关包的依赖即可,SpringCLoud则会采用RestTemplate来发送Rest请求。这也说明了eureka其是基于Servlet实现的。

jersey启动

SpringCloud在容器启动的时候,动态添加过滤器servletContainer 并拦截/eureka/* 的url。在该过滤器初始化的时候便加载了com.sun.jersey.spi.container.servlet.ServletContainer 该filter包含的classes有:作为jersey的WebComponentResourceConfig
- com.netflix.eureka.resources.ASGResource
- com.netflix.discovery.provider.DiscoveryJerseyProvider
- com.netflix.eureka.resources.ApplicationsResource
- com.netflix.eureka.resources.StatusResource
- com.netflix.eureka.resources.PeerReplicationResource
- com.netflix.eureka.resources.VIPResource
- com.netflix.eureka.resources.ServerInfoResource
- com.netflix.eureka.resources.InstancesResource
- com.netflix.eureka.resources.SecureVIPResource

再通过WebApplicationProvider初始化jersey服务。具体实现为WebApplicationImpl._initiate方法。

WebApplicationFactory

public static WebApplication createWebApplication() throws ContainerException {
    for (WebApplicationProvider wap : ServiceFinder.find(WebApplicationProvider.class)) {
        // Use the first provider found
        // 创建jersey服务
        return wap.createWebApplication();
    }
    throw new ContainerException("No WebApplication provider is present");
}

再由com.sun.jersey.core.spi.component.ProviderFactory 通过反射实例化com.netflix.discovery.provider.DiscoveryJerseyProvider负责将对象实例化和反序列化发送到eureka服务器

    private ComponentProvider __getComponentProvider(Class c) {
        try {
            ComponentInjector ci = new ComponentInjector(this.ipc, c);
            ComponentConstructor cc = new ComponentConstructor(this.ipc, c, ci);
            // 实例化Provider
            Object o = cc.getInstance();
            return new ProviderFactory.SingletonComponentProvider(ci, o);
        } catch (NoClassDefFoundError var5) {
            LOGGER.log(Level.CONFIG, "A dependent class, " + var5.getLocalizedMessage() + ", of the component " + c + " is not found." + " The component is ignored.");
            return null;
        } catch (InvocationTargetException var6) {
            if(var6.getCause() instanceof NoClassDefFoundError) {
                NoClassDefFoundError ncdf = (NoClassDefFoundError)var6.getCause();
                LOGGER.log(Level.CONFIG, "A dependent class, " + ncdf.getLocalizedMessage() + ", of the component " + c + " is not found." + " The component is ignored.");
                return null;
            } else {
                LOGGER.log(Level.SEVERE, "The provider class, " + c + ", could not be instantiated. Processing will continue but the class will not be utilized", var6.getTargetException());
                return null;
            }
        } catch (Exception var7) {
            LOGGER.log(Level.SEVERE, "The provider class, " + c + ", could not be instantiated. Processing will continue but the class will not be utilized", var7);
            return null;
        }
    }

配置信息加载

ConfigurationClassEnhancer
- 负责注解Configuration和注解Bean等的实例化,如:
- WebMvcConfigurationSupport根据classpath中是否存在gson、jackson等来
- ArchaiusAutoConfiguration加载archaius配置信息

protected void configureArchaius(ConfigurableEnvironmentConfiguration envConfig) {
    if (initialized.compareAndSet(false, true)) {
        // 获取appName 没有配置则用默认
        String appName = this.env.getProperty("spring.application.name");
        if (appName == null) {
            appName = "application";
            log.warn("No spring.application.name found, defaulting to 'application'");
        }
        // 后面代码省略...    
    }
}    

DefaultListableBeanFactory基于代理实例化eureka组件等,如配置信息,EurekaClient

EurekaClientAutoConfiguration InstanceInfo EurekaClient

EurekaClientConfigBean

SpringCloud对EurekaClient的配置项

EurekaClientAutoConfiguration设置向eureka server或者向其他服务发现组件 注册需要的信息即InstanceInfo,此处为SpringCloud做的适配。

@ProvidedBy(EurekaConfigBasedInstanceInfoProvider.class)
@Serializer("com.netflix.discovery.converters.EntityBodyConverter")
@XStreamAlias("instance")// xml格式的节点
@JsonRootName("instance")// json格式的节点
public class InstanceInfo {//代码省略...}

@Bean
@ConditionalOnMissingBean(value = ApplicationInfoManager.class, search = SearchStrategy.CURRENT)
@org.springframework.cloud.context.config.annotation.RefreshScope
@Lazy
public ApplicationInfoManager eurekaApplicationInfoManager(EurekaInstanceConfig config) {
    // 实例化向注册表注册所需要的信息,如eureka主页地址、本机ip、appName等
    InstanceInfo instanceInfo = new InstanceInfoFactory().create(config);
    return new ApplicationInfoManager(config, instanceInfo);
}

EurekaClientAutoConfiguration实例化EurekaClient,设置配置信息,PropertyBasedClientConfigConstants为配置变量名,以及一些默认值,实现类为DiscoveryClient
- EurekaClientAutoConfiguration 源码


@Bean(destroyMethod = "shutdown")
@ConditionalOnMissingBean(value = EurekaClient.class, search = SearchStrategy.CURRENT)
@org.springframework.cloud.context.config.annotation.RefreshScope
@Lazy
public EurekaClient eurekaClient(ApplicationInfoManager manager, EurekaClientConfig config, EurekaInstanceConfig instance) {
    manager.getInfo(); // force initialization
    return new CloudEurekaClient(manager, config, this.optionalArgs,
                    this.context);
}

也会通过EurekaAutoServiceRegistration,将服务自动注册到SpringCloud的服务发现注册框架,定时进行健康检查。

@Bean
@ConditionalOnBean(AutoServiceRegistrationProperties.class)
@ConditionalOnProperty(value = "spring.cloud.service-registry.auto-registration.enabled", matchIfMissing = true)
public EurekaAutoServiceRegistration eurekaAutoServiceRegistration(ApplicationContext context, EurekaServiceRegistry registry, EurekaRegistration registration) {
    return new EurekaAutoServiceRegistration(context, registry, registration);
}

DiscoveryClient 源码

  • DiscoveryClient 源码

    • 继承关系

    DiscoveryClient 实现了 EurekaClient,EurekaClient继承了LookupService
    SpringCloud Eureka 源码分析_第1张图片

    DiscoveryClient主要负责与eureka server交互,需要配置servers的url,支持故障转移。eureka client 的主要作用有:

    • 注册实力到eureka server
    • 向eureka server 续租约
    • 在cleint关闭时,取消同eureka server的租约
    • 查询eureka server中的注册信息

    EurekaClient定义一个简单的接口,给DiscoveryClient实现,主要为了兼容eureka 1.x版本,使得1.x版本更容易过渡到2.x版本,主要作用有:

    • 提供各种不同的方式,以获取各种InstanceInfos的能力
    • 提供获取客户端数据的能力,如获取regions等。
    • 提供注册和访问健康检查的能力

    LookupService提供查询所有活动的Instances的接口。

@Inject
DiscoveryClient(ApplicationInfoManager applicationInfoManager, EurekaClientConfig config, AbstractDiscoveryClientOptionalArgs args,
                Provider backupRegistryProvider) {
    if (args != null) {
        this.healthCheckHandlerProvider = args.healthCheckHandlerProvider;
        this.healthCheckCallbackProvider = args.healthCheckCallbackProvider;
        this.eventListeners.addAll(args.getEventListeners());
    } else {
        this.healthCheckCallbackProvider = null;
        this.healthCheckHandlerProvider = null;
    }
    // 向注册表注册所需的信息,提供了各种注册组件需要的实现,也可以自己自定义实现,如
    // AbstractInstanceConfig提供了大量的默认信息;
    // MyDataCenterInstanceConfigProvider提供非aws的数据中心;
    // CloudInstanceConfigProvider提供aws注册所需的;
    // EurekaInstanceConfig提供了向eureka注册所需的;
    // ApplicationInfoManager 使用的是EurekaInstanceConfig
    this.applicationInfoManager = applicationInfoManager;
    InstanceInfo myInfo = applicationInfoManager.getInfo();
    // 客户端配置 大部分信息采用DefaultEurekaClientConfig
    clientConfig = config;
    // 已经过时,主要为了兼容遗留客户端问题
    staticClientConfig = clientConfig;
    // 传输层如http请求超时、重试等信息
    transportConfig = config.getTransportConfig();
    // 该eureka实例的信息 如主机信息,健康检测接口等
    instanceInfo = myInfo;
    if (myInfo != null) {
        // 服务唯一地址  如:EUREKA-SERVER/172.16.17.60:eureka-server:30000
        appPathIdentifier = instanceInfo.getAppName() + "/" + instanceInfo.getId();
    } else {
        logger.warn("Setting instanceInfo to a passed in null value");
    }
    // 备份注册表信息,当服务端不可用时,客户度可以从这里获取注册表信息
    this.backupRegistryProvider = backupRegistryProvider;
    // 如果eureka server的地址来源dns服务,则随机获取urls
    this.urlRandomizer = new EndpointUtils.InstanceInfoBasedUrlRandomizer(instanceInfo);
    // 采用cas Applications存放的时服务器返回的存储客户端注册信息的
    localRegionApps.set(new Applications());
    // cas 递增版本,防止客户端注册旧的信息
    fetchRegistryGeneration = new AtomicLong(0);

    remoteRegionsToFetch = new AtomicReference(clientConfig.fetchRegistryForRemoteRegions());
    remoteRegionsRef = new AtomicReference<>(remoteRegionsToFetch.get() == null ? null : remoteRegionsToFetch.get().split(","));
    // 判断是否需要从eureka server 获取注册表信息 并初始化相应的度量信息
    if (config.shouldFetchRegistry()) {
        this.registryStalenessMonitor = new ThresholdLevelsMetric(this, METRIC_REGISTRY_PREFIX + "lastUpdateSec_", new long[]{15L, 30L, 60L, 120L, 240L, 480L});
    } else {
        this.registryStalenessMonitor = ThresholdLevelsMetric.NO_OP_METRIC;
    }
    // 是否需要将信息注册到eureka server上,通过这个开关可以实现,
    // 只获取其他实例的信息,而不将自己的信息给其他客户端发现
    if (config.shouldRegisterWithEureka()) {
        this.heartbeatStalenessMonitor = new ThresholdLevelsMetric(this, METRIC_REGISTRATION_PREFIX + "lastHeartbeatSec_", new long[]{15L, 30L, 60L, 120L, 240L, 480L});
    } else {
        this.heartbeatStalenessMonitor = ThresholdLevelsMetric.NO_OP_METRIC;
    }

    // 属于aws的基础概念regin和zone 默认值为us-east-1
    logger.info("Initializing Eureka in region {}", clientConfig.getRegion());

    // 如果不需要注册信息到server和拉取注册信息表,则初始化完成。
    if (!config.shouldRegisterWithEureka() && !config.shouldFetchRegistry()) {
        logger.info("Client configured to neither register nor query for data.");
        scheduler = null;
        heartbeatExecutor = null;
        cacheRefreshExecutor = null;
        eurekaTransport = null;
        instanceRegionChecker = new InstanceRegionChecker(new PropertyBasedAzToRegionMapper(config), clientConfig.getRegion());

        // This is a bit of hack to allow for existing code using DiscoveryManager.getInstance()
        // to work with DI'd DiscoveryClient
        DiscoveryManager.getInstance().setDiscoveryClient(this);
        DiscoveryManager.getInstance().setEurekaClientConfig(config);

        initTimestampMs = System.currentTimeMillis();

        logger.info("Discovery Client initialized at timestamp {} with initial instances count: {}",
                initTimestampMs, this.getApplications().size());
        return;  // no need to setup up an network tasks and we are done
    }

    try {
        // 初始化调度线程池 3个核心线程 并且为后台运行。主要负责:
        // server 的url更新
        // 调度TimedSuperVisorTask被该TimerTask所包裹的线程必须是线程安全的,负责在子任务超时时,强制子任务超时。
        scheduler = Executors.newScheduledThreadPool(3,
                new ThreadFactoryBuilder()
                        .setNameFormat("DiscoveryClient-%d")
                        .setDaemon(true)
                        .build());
        // 实例化心跳线程池,1个核心线程,默认最大的线程数为5个,使用直接提交线程
        heartbeatExecutor = new ThreadPoolExecutor(
                1, clientConfig.getHeartbeatExecutorThreadPoolSize(), 0, TimeUnit.SECONDS,
                new SynchronousQueue(),
                new ThreadFactoryBuilder()
                        .setNameFormat("DiscoveryClient-HeartbeatExecutor-%d")
                        .setDaemon(true)
                        .build()
        );  // use direct handoff
        // 实例化注册表缓存刷新线程池,最大线程数默认5个
        cacheRefreshExecutor = new ThreadPoolExecutor(
                1, clientConfig.getCacheRefreshExecutorThreadPoolSize(), 0, TimeUnit.SECONDS,
                new SynchronousQueue(),
                new ThreadFactoryBuilder()
                        .setNameFormat("DiscoveryClient-CacheRefreshExecutor-%d")
                        .setDaemon(true)
                        .build()
        );  // use direct handoff

        eurekaTransport = new EurekaTransport();
        // 调度服务端端点
        scheduleServerEndpointTask(eurekaTransport, args);
        // 支持dns和配置 做region映射
        AzToRegionMapper azToRegionMapper;
        if (clientConfig.shouldUseDnsForFetchingServiceUrls()) {
            azToRegionMapper = new DNSBasedAzToRegionMapper(clientConfig);
        } else {
            azToRegionMapper = new PropertyBasedAzToRegionMapper(clientConfig);
        }
        if (null != remoteRegionsToFetch.get()) {
            azToRegionMapper.setRegionsToFetch(remoteRegionsToFetch.get().split(","));
        }
        instanceRegionChecker = new InstanceRegionChecker(azToRegionMapper, clientConfig.getRegion());
    } catch (Throwable e) {
        throw new RuntimeException("Failed to initialize DiscoveryClient!", e);
    }
    // fetchRegistry方法,第一次获取全部,之后是增量获取,也可以通过true参数强制全量获取,如果获取成功则返回true,如果客户端和服务端存在问题,则会返回false
    // 如果需要拉取注册表,且获取注册表失败,则从本地备份中获取注册信息,也就是ca的高可用的实现。
    if (clientConfig.shouldFetchRegistry() && !fetchRegistry(false)) {
        fetchRegistryFromBackup();
    }
    // 初始化所有的调度任务 代码见下面
    initScheduledTasks();
    try {
        Monitors.registerObject(this);
    } catch (Throwable e) {
        logger.warn("Cannot register timers", e);
    }

    // This is a bit of hack to allow for existing code using DiscoveryManager.getInstance()
    // to work with DI'd DiscoveryClient
    DiscoveryManager.getInstance().setDiscoveryClient(this);
    DiscoveryManager.getInstance().setEurekaClientConfig(config);

    initTimestampMs = System.currentTimeMillis();
    logger.info("Discovery Client initialized at timestamp {} with initial instances count: {}",
            initTimestampMs, this.getApplications().size());
}
  • initScheduledTasks 初始化调度任务源码
/**
 * Initializes all scheduled tasks.
 */
private void initScheduledTasks() {
    // 是否需要拉取注册表,刷新本地缓存注册表
    if (clientConfig.shouldFetchRegistry()) {
        // registry cache refresh timer
        // 默认30秒
        int registryFetchIntervalSeconds = clientConfig.getRegistryFetchIntervalSeconds();
        // 超时后,再次调用的时间间隔基数,默认为10,具体算法可以参考 TimedSupervisorTask的run方法。
        int expBackOffBound = clientConfig.getCacheRefreshExecutorExponentialBackOffBound();
        /**
        public TimedSupervisorTask(String name, ScheduledExecutorService scheduler, ThreadPoolExecutor executor,
                                   int timeout, TimeUnit timeUnit, int expBackOffBound, Runnable task) {
            // 代码省略...
            this.timeoutMillis = timeUnit.toMillis(timeout);
            this.delay = new AtomicLong(timeoutMillis);
            this.maxDelay = timeoutMillis * expBackOffBound;
            // 代码省略...
        }

        public void run() {
            Future future = null;
            try {
                // ...
                delay.set(timeoutMillis);
                // ...
            } catch (TimeoutException e) {
                logger.error("task supervisor timed out", e);
                timeoutCounter.increment();
                long currentDelay = delay.get();
                long newDelay = Math.min(maxDelay, currentDelay * 2);
                delay.compareAndSet(currentDelay, newDelay);
            }
            // ...
        }
        **/
        scheduler.schedule(
                new TimedSupervisorTask(
                        "cacheRefresh",
                        scheduler,
                        cacheRefreshExecutor,
                        registryFetchIntervalSeconds,
                        TimeUnit.SECONDS,
                        expBackOffBound,
                        new CacheRefreshThread()
                ),
                registryFetchIntervalSeconds, TimeUnit.SECONDS);
    }
    // 是否发送心跳信息到server,即续约
    if (clientConfig.shouldRegisterWithEureka()) {
        // 每次发起续约时间 默认30秒
        int renewalIntervalInSecs = instanceInfo.getLeaseInfo().getRenewalIntervalInSecs();
        // 默认为10 同理
        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);

        // 更新和同步本地信息到eureka server
        // InstanceInfo replicator
        instanceInfoReplicator = new InstanceInfoReplicator(
                this,
                instanceInfo,
                // 默认30秒
                clientConfig.getInstanceInfoReplicationIntervalSeconds(),
                // 执行速度受限burstSize
                2); // burstSize
        // 状态改变监听器,在下面配置是否需要使用该触发器
        statusChangeListener = new ApplicationInfoManager.StatusChangeListener() {
            @Override
            public String getId() {
                return "statusChangeListener";
            }

            @Override
            public void notify(StatusChangeEvent statusChangeEvent) {
                if (InstanceStatus.DOWN == statusChangeEvent.getStatus() ||
                        InstanceStatus.DOWN == statusChangeEvent.getPreviousStatus()) {
                    // log at warn level if DOWN was involved
                    logger.warn("Saw local status change event {}", statusChangeEvent);
                } else {
                    logger.info("Saw local status change event {}", statusChangeEvent);
                }
                // 触发更新和同步本地消息到erueka server
                instanceInfoReplicator.onDemandUpdate();
            }
        };
        // 是否注册本地状态改变触发器,如果不注册,则不会将本地状态更新,同步到server
        if (clientConfig.shouldOnDemandUpdateStatusChange()) {
            applicationInfoManager.registerStatusChangeListener(statusChangeListener);
        }
        // 设置多少秒后,启动instanceInfoReplicator,默认为40秒
        instanceInfoReplicator.start(clientConfig.getInitialInstanceInfoReplicationIntervalSeconds());
    } else {
        logger.info("Not registering with Eureka server per configuration");
    }
}
  • CloudEurekaClient 主要负责发送心跳信息,方法为onCacheRefreshed
@Override
protected void onCacheRefreshed() {
    if (this.cacheRefreshedCount != null) { //might be called during construction and will be null
        long newCount = this.cacheRefreshedCount.incrementAndGet();
        log.trace("onCacheRefreshed called with count: " + newCount);
        this.publisher.publishEvent(new HeartbeatEvent(this, newCount));
    }
}

EurekaServer初始化 EurekaServerAutoConfiguration

EurekaServerConfigBean

SpringCloud关于EurekaServer的配置项

SpringCloud的InstanceRegistry与eureka的InstanceRegistry

  • SpringCloud通过EurekaServerAutoConfiguration适配,初始化eureka server的信息,即InstanceRegistry

  • InstanceRegistry类图(此类为SpringCloud定义的InstanceRegistry而非netflix的)

    • LeaseManager 主要负责租约的管理,如创建、更新和删除。
    • LookupService 见LookupService
    • AbstractInstanceRegistry 处理所有来自eureka client的注册请求
    • 提供注册、续约、注销、过期处理、状态改变处理
    • 增量存储注册表
    • PeerAwareInstanceRegistry
    • 处理Eureka Server节点间的同步
    • 如果Eureka Server启动的时候会从其他节点获取注册表信息,如果获取失败,Eureka Server会不允许用户在指定的一段时间(EurekaServerConfig.getWaitTimeInMsWhenSyncEmpty)里获取注册表信息。
    • 自我保护实现

SpringCloud Eureka 源码分析_第2张图片

  • 初始化Eureka Server
    EurekaServerAutoConfiguration 源码
@Bean
public PeerAwareInstanceRegistry peerAwareInstanceRegistry(
        ServerCodecs serverCodecs) {
    this.eurekaClient.getApplications(); // force initialization
    // 父类AbstractInstanceRegistry初始化一个新的空信息的注册表
    return new InstanceRegistry(this.eurekaServerConfig, this.eurekaClientConfig,
            serverCodecs, this.eurekaClient,
            //每分钟更新次数,默认为1
            this.instanceRegistryProperties.getExpectedNumberOfRenewsPerMin(),
            //确认取消租约时的值,默认为1 
            this.instanceRegistryProperties.getDefaultOpenForTrafficCount());
}

// AbstractInstanceRegistry
/**
 * Create a new, empty instance registry.
 */
protected AbstractInstanceRegistry(EurekaServerConfig serverConfig, EurekaClientConfig clientConfig, ServerCodecs serverCodecs) {
    this.serverConfig = serverConfig;
    this.clientConfig = clientConfig;
    this.serverCodecs = serverCodecs;
    // 用于统计
    this.recentCanceledQueue = new CircularQueue>(1000);
    this.recentRegisteredQueue = new CircularQueue>(1000);
    // 记录最近一次更新,每隔1分钟更新一次
    this.renewsLastMin = new MeasuredRate(1000 * 60 * 1);
    // 清理过期增量信息的调度器
    this.deltaRetentionTimer.schedule(getDeltaRetentionTask(),
            // 默认30秒
            serverConfig.getDeltaRetentionTimerIntervalInMs(),
            // 默认30秒
            serverConfig.getDeltaRetentionTimerIntervalInMs());
}

// getDeltaRetentionTask
private TimerTask getDeltaRetentionTask() {
    return new TimerTask() {

        @Override
        public void run() {
            Iterator it = recentlyChangedQueue.iterator();
            while (it.hasNext()) {
                // getRetentionTimeInMSInDeltaQueue 保持增量信息的缓存时间 默认为3分钟
                if (it.next().getLastUpdateTime() <
                        System.currentTimeMillis() - serverConfig.getRetentionTimeInMSInDeltaQueue()) {
                    it.remove();
                } else {
                    break;
                }
            }
        }

    };
}

// PeerAwareInstanceRegistryImpl
@Inject
public PeerAwareInstanceRegistryImpl(
        EurekaServerConfig serverConfig,
        EurekaClientConfig clientConfig,
        ServerCodecs serverCodecs,
        EurekaClient eurekaClient
) {
    super(serverConfig, clientConfig, serverCodecs);
    this.eurekaClient = eurekaClient;
    // 分片数量最近一次更新,每个1分钟更新一次
    this.numberOfReplicationsLastMin = new MeasuredRate(1000 * 60 * 1);
    // We first check if the instance is STARTING or DOWN, then we check explicit overrides,
    // then we check the status of a potentially existing lease.
    // 先检查实例是启动或者关闭(DownOrStartingRule),然后再检查优先级(InstanceStatus),再检查可能存在的租约的状态(LeaseExistsRule)UP或者OUT_OF_SERVICE
    // FirstMatchWinsCompositeRule 从状态列表里查找第一个匹配的,如果状态列表都没有匹配的,则使用AlwaysMatchInstanceStatusRule返回默认的状态UP
    this.instanceStatusOverrideRule = new FirstMatchWinsCompositeRule(new DownOrStartingRule(),
            new OverrideExistsRule(overriddenInstanceStatusMap), new LeaseExistsRule());
}

EurekaServerContext

DefaultEurekaServerContext 继承 EurekaServerContext
- 初始化DefaultEurekaServerContext
- 本地Eureka Server的上下文,以及暴露给其他组件访问的服务,如注册
- DefaultEurekaServerContext源码

@PostConstruct
@Override
public void initialize() throws Exception {
    logger.info("Initializing ...");
    // PeerEurekaNode 负责复制所有的节点更新操作。Server端节点更新的主要实现类
    //启动管理同等的eureka节点(PeerEurekaNode)的生命周期 调度器
    peerEurekaNodes.start();
    // 根据节点初始化InstanceRegistry,完成服务端的初始化
    registry.init(peerEurekaNodes);
    logger.info("Initialized");
}

// PeerEurekaNodes
public void start() {
    taskExecutor = Executors.newSingleThreadScheduledExecutor(
            new ThreadFactory() {
                @Override
                public Thread newThread(Runnable r) {
                    Thread thread = new Thread(r, "Eureka-PeerNodesUpdater");
                    thread.setDaemon(true);
                    return thread;
                }
            }
    );
    try {
        // 先解析url,再更新节点信息,先销毁原来的,再更新
        updatePeerEurekaNodes(resolvePeerUrls());
        // 定义更新任务
        Runnable peersUpdateTask = new Runnable() {
            @Override
            public void run() {
                try {
                    updatePeerEurekaNodes(resolvePeerUrls());
                } catch (Throwable e) {
                    logger.error("Cannot update the replica Nodes", e);
                }

            }
        };
        // 调度更新任务
        taskExecutor.scheduleWithFixedDelay(
                peersUpdateTask,
                // 节点跟新间隔 默认 10分钟
                serverConfig.getPeerEurekaNodesUpdateIntervalMs(),
                serverConfig.getPeerEurekaNodesUpdateIntervalMs(),
                TimeUnit.MILLISECONDS
        );
    } catch (Exception e) {
        throw new IllegalStateException(e);
    }
    for (PeerEurekaNode node : peerEurekaNodes) {
        logger.info("Replica node URL:  " + node.getServiceUrl());
    }
}

//PeerEurekaNodes
protected void updatePeerEurekaNodes(List newPeerUrls) {
    if (newPeerUrls.isEmpty()) {
        logger.warn("The replica size seems to be empty. Check the route 53 DNS Registry");
        return;
    }
    // 获取旧的urls,准备关闭
    Set toShutdown = new HashSet<>(peerEurekaNodeUrls);
    toShutdown.removeAll(newPeerUrls);
    // 添加新的urls
    Set toAdd = new HashSet<>(newPeerUrls);
    toAdd.removeAll(peerEurekaNodeUrls);
    // 如果发现没改变,则直接返回
    if (toShutdown.isEmpty() && toAdd.isEmpty()) { // No change
        return;
    }

    // Remove peers no long available
    List newNodeList = new ArrayList<>(peerEurekaNodes);
    // 关闭,移除不再可用的urls
    if (!toShutdown.isEmpty()) {
        logger.info("Removing no longer available peer nodes {}", toShutdown);
        int i = 0;
        while (i < newNodeList.size()) {
            PeerEurekaNode eurekaNode = newNodeList.get(i);
            if (toShutdown.contains(eurekaNode.getServiceUrl())) {
                newNodeList.remove(i);
                eurekaNode.shutDown();
            } else {
                i++;
            }
        }
    }

    // Add new peers
    if (!toAdd.isEmpty()) {
        logger.info("Adding new peer nodes {}", toAdd);
        for (String peerUrl : toAdd) {
            // 创建新的urls
            newNodeList.add(createPeerEurekaNode(peerUrl));
        }
    }

    this.peerEurekaNodes = newNodeList;
    this.peerEurekaNodeUrls = new HashSet<>(newPeerUrls);
}

// createPeerEurekaNode
protected PeerEurekaNode createPeerEurekaNode(String peerEurekaNodeUrl) {
    // 创建Jersey 客户端 发送注册、状态更新、心跳等请求
    HttpReplicationClient replicationClient = JerseyReplicationClient.createReplicationClient(serverConfig, serverCodecs, peerEurekaNodeUrl);
    String targetHost = hostFromUrl(peerEurekaNodeUrl);
    if (targetHost == null) {
        targetHost = "host";
    }
    // 创建PeerEurekaNode
    return new PeerEurekaNode(registry, targetHost, peerEurekaNodeUrl, replicationClient, serverConfig);
}

// PeerEurekaNode
PeerEurekaNode(PeerAwareInstanceRegistry registry, String targetHost, String serviceUrl,
                                     HttpReplicationClient replicationClient, EurekaServerConfig config,
                                     int batchSize, long maxBatchingDelayMs,
                                     long retrySleepTimeMs, long serverUnavailableSleepTimeMs) {
    this.registry = registry;
    this.targetHost = targetHost;
    this.replicationClient = replicationClient;

    this.serviceUrl = serviceUrl;
    this.config = config;
    this.maxProcessingDelayMs = config.getMaxTimeForReplication();
    // 获取批处理名称,根据配置的url获取hostName,如果hostName失败,则直接采用配置的url做为批处理名称
    String batcherName = getBatcherName();
    // 执行任务的客户端需要实现的接口,提供了两个接口,一个处理单个任务,一个处理多个任务,都会在同一个时间执行,多个任务的会聚合多个任务的返回结果,且返回的类型是一样的。处理结果有Success, Congestion, TransientError(任务失败,但过会会重试), PermanentError(任务失败,且不再重试)
    ReplicationTaskProcessor taskProcessor = new ReplicationTaskProcessor(targetHost, replicationClient);
    // 创建批处理任务
    this.batchingDispatcher = TaskDispatchers.createBatchingTaskDispatcher(
            batcherName,
            // 批处理任务最大的缓冲区大小,默认1万个,如果超出,则会判断哪些任务已经过期,过期则移除,添加新的任务
            config.getMaxElementsInPeerReplicationPool(),
            // 最大批处理个数,250
            batchSize,
            // 分配给副本的最大线程数 默认20
            config.getMaxThreadsForPeerReplication(),
            // 最大的批处理间隔 500毫秒
            maxBatchingDelayMs,
            // 服务不可用后,休息1秒
            serverUnavailableSleepTimeMs,
            // 网络异常后,每个100毫秒进行重试
            retrySleepTimeMs,
            // 处理任务
            taskProcessor
    );
    this.nonBatchingDispatcher = TaskDispatchers.createNonBatchingTaskDispatcher(
            targetHost,
            // 缓冲区 1万
            config.getMaxElementsInStatusReplicationPool(),
            // 分配给副本处理状态的最大线程数 默认1个
            config.getMaxThreadsForStatusReplication(),
            // 最大的批处理间隔 500毫秒
            maxBatchingDelayMs,
            serverUnavailableSleepTimeMs,
            retrySleepTimeMs,
            taskProcessor
    );
}

//PeerAwareInstanceRegistry
@Override
public void init(PeerEurekaNodes peerEurekaNodes) throws Exception {
    // 启动更新记录器
    this.numberOfReplicationsLastMin.start();
    this.peerEurekaNodes = peerEurekaNodes;
    // 初始化注册信息缓存,以供客户端查询使用,采用的是Guava的堆缓存,具体实现类 ResponseCacheImpl
    initializedResponseCache();
    // 启动检查是否因为网络分区导致的更新急剧下降,从而防止服务被误删,即自我保护模式
    scheduleRenewalThresholdUpdateTask();
    // 初始化获取其他Region区域的注册表信息的调度器
    initRemoteRegionRegistry();

    try {
        Monitors.registerObject(this);
    } catch (Throwable e) {
        logger.warn("Cannot register the JMX monitor for the InstanceRegistry :", e);
    }
}
PeerEurekaNodes 与 PeerEurekaNode

PeerEurekaNodes类图
SpringCloud Eureka 源码分析_第3张图片
PeerEurekaNode类图
SpringCloud Eureka 源码分析_第4张图片
- PeerEurekaNodes 通过start方法开启 管理 PeerEurekaNode的生命周期调度
- PeerEurekaNode 节点间交互的主要实现类,如心跳等
- 定义了当网络异常后,每隔100毫秒进行重试
- 如果服务器不可用,进行1秒的休眠
- 定义最大的批处理调度间隔 500毫秒
- 定义了最大的批处理请求250

EurekaServerBootstrap

初始化完成EurekaServerContext后,接下来创建EurekaServerBootstrap(其同com.netflix.eureka.EurekaBootStrap的代码基本一致,netflix是通过监听ServletContextListener事件来启动eureka server),
再通过EurekaServerInitializerConfiguration启动线程调用EurekaServerBootstrap的contextInitialized方法

@Override
public void start() {
    new Thread(new Runnable() {
        @Override
        public void run() {
            try {
                //TODO: is this class even needed now?
                // 调用contextInitialized Eureka Server进行初始化
                eurekaServerBootstrap.contextInitialized(EurekaServerInitializerConfiguration.this.servletContext);
                log.info("Started Eureka Server");
                // 发布相关事件
                publish(new EurekaRegistryAvailableEvent(getEurekaServerConfig()));
                EurekaServerInitializerConfiguration.this.running = true;
                publish(new EurekaServerStartedEvent(getEurekaServerConfig()));
            }
            catch (Exception ex) {
                // Help!
                log.error("Could not initialize Eureka servlet context", ex);
            }
        }
    }).start();
}

// EurekaServerBootstrap
public void contextInitialized(ServletContext context) {
    try {
        // 初始化环境配置信息,eureka.datacenter和eureka.environment,如果没配置第一个为default,第二个为test
        initEurekaEnvironment();
        // 初始化Eureka Server Context,调用PeerAwareInstanceRegistryImpl同步注册信息
        initEurekaServerContext();
        // 将EurekaServerContext设置到ServletContext中
        context.setAttribute(EurekaServerContext.class.getName(), this.serverContext);
    }
    catch (Throwable e) {
        log.error("Cannot bootstrap eureka server :", e);
        throw new RuntimeException("Cannot bootstrap eureka server :", e);
    }
}

至此,eureka server也就启动完成了,接下来都是通过调度来实现交互。

总结

  • EurekaClient的责任主要提现在DiscoveryClient类的实现;
  • EurekaServer的责任主要体现在PeerEurekaNodes与PeerEurekaNode类的实现;

  • 其他相关类:

    • 客户端配置:EurekaClientConfigBean
    • 服务端配置:EurekaServerConfigBean
    • 服务注册操作类:SpringCloud与netflix的InstanceRegistry
    • EurekaServer启动类:SpringCloud的EurekaServerBootstrap

你可能感兴趣的:(java,eureka,源码)