SpringCloud 源码系列(6)— 注册中心Eureka 之 总结篇

2,839 阅读16分钟

系列文章:

SpringCloud 源码系列(1)— 注册中心Eureka 之 启动初始化

SpringCloud 源码系列(2)— 注册中心Eureka 之 服务注册、续约

SpringCloud 源码系列(3)— 注册中心Eureka 之 抓取注册表

SpringCloud 源码系列(4)— 注册中心Eureka 之 服务下线、故障、自我保护机制

SpringCloud 源码系列(5)— 注册中心Eureka 之 EurekaServer集群

SpringCloud Eureka

到这里,对 Eureka 核心源码的研究就差不多了,这节先来看下 Spring cloud eureka。Spring cloud eureka 提供了服务端的依赖 spring-cloud-starter-netflix-eureka-server 和客户端的依赖 spring-cloud-starter-netflix-eureka-client,这两个依赖包本身是比较简单的,只是对 netflix 的 eureka-server 和 eureka-client 的封装,它通过一些注解和配置类将 eureka 整合到 springboot 技术栈中,便于使用。

spring-cloud-starter-netflix-eureka-server

spring-cloud-starter-netflix-eureka-server 要从 @EnableEurekaServer 这个注解来看,因为我们的注册中心是基于 springboot 的,在启动类上加上了 @EnableEurekaServer 注解就启用了 eureka-server 注册中心。

1、Eureka Server 自动化配置

看这个注解的定义,从注释中可以了解到,这个注解会激活 EurekaServerAutoConfiguration 的自动化配置类。

/**
 * Annotation to activate Eureka Server related configuration.
 * {@link EurekaServerAutoConfiguration}
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(EurekaServerMarkerConfiguration.class)
public @interface EnableEurekaServer {

}

看 EurekaServerAutoConfiguration 这个类,可以发现 springcloud 几乎是将 com.netflix.eureka.EurekaBootStrap 中初始化组件的代码拷贝到了 EurekaServerAutoConfiguration,然后以 springboot 创建 bean 的方式来创建相关的组件。

@Configuration(proxyBeanMethods = false)
@Import(EurekaServerInitializerConfiguration.class)
@ConditionalOnBean(EurekaServerMarkerConfiguration.Marker.class)
@EnableConfigurationProperties({ EurekaDashboardProperties.class,
        InstanceRegistryProperties.class })
@PropertySource("classpath:/eureka/server.properties")
public class EurekaServerAutoConfiguration implements WebMvcConfigurer {
    /**
     * List of packages containing Jersey resources required by the Eureka server.
     */
    private static final String[] EUREKA_PACKAGES = new String[] {"com.netflix.discovery", "com.netflix.eureka" };

    @Autowired
    private ApplicationInfoManager applicationInfoManager;
    @Autowired
    private EurekaServerConfig eurekaServerConfig;
    @Autowired
    private EurekaClientConfig eurekaClientConfig;
    @Autowired
    private EurekaClient eurekaClient;
    @Autowired
    private InstanceRegistryProperties instanceRegistryProperties;

    @Bean
    @ConditionalOnProperty(prefix = "eureka.dashboard", name = "enabled", matchIfMissing = true)
    public EurekaController eurekaController() {
        return new EurekaController(this.applicationInfoManager);
    }

    @Bean
    public ServerCodecs serverCodecs() {
        return new CloudServerCodecs(this.eurekaServerConfig);
    }

    @Bean
    public PeerAwareInstanceRegistry peerAwareInstanceRegistry(ServerCodecs serverCodecs) {
        this.eurekaClient.getApplications(); // force initialization
        return new InstanceRegistry(this.eurekaServerConfig, this.eurekaClientConfig,
                serverCodecs, this.eurekaClient,
                this.instanceRegistryProperties.getExpectedNumberOfClientsSendingRenews(),
                this.instanceRegistryProperties.getDefaultOpenForTrafficCount());
    }

    @Bean
    @ConditionalOnMissingBean
    public PeerEurekaNodes peerEurekaNodes(PeerAwareInstanceRegistry registry,
            ServerCodecs serverCodecs, ReplicationClientAdditionalFilters replicationClientAdditionalFilters) {
        return new RefreshablePeerEurekaNodes(registry, this.eurekaServerConfig,
                this.eurekaClientConfig, serverCodecs, this.applicationInfoManager,
                replicationClientAdditionalFilters);
    }

    @Bean
    @ConditionalOnMissingBean
    public EurekaServerContext eurekaServerContext(ServerCodecs serverCodecs,
            PeerAwareInstanceRegistry registry, PeerEurekaNodes peerEurekaNodes) {
        return new DefaultEurekaServerContext(this.eurekaServerConfig, serverCodecs,
                registry, peerEurekaNodes, this.applicationInfoManager);
    }

    @Bean
    public EurekaServerBootstrap eurekaServerBootstrap(PeerAwareInstanceRegistry registry,
            EurekaServerContext serverContext) {
        return new EurekaServerBootstrap(this.applicationInfoManager,
                this.eurekaClientConfig, this.eurekaServerConfig, registry,
                serverContext);
    }

    //.....
}

但是要注意,springcloud 中,感知集群的注册表组件是 InstanceRegistry,集群 PeerEurekaNodes 组件是 RefreshablePeerEurekaNodes

2、Springboot 方式的配置

EurekaServerAutoConfiguration 中注入了 EurekaServerConfig、EurekaClientConfig,在 Netflix 中,EurekaServerConfig 默认读取的是 eureka-server.properties 配置文件,EurekaClientConfig 默认读取的是 eureka-client.properties 配置文件。而在 springcloud 中,它们的实现类为 EurekaServerConfigBean、EurekaClientConfigBean,可以看到,就是基于 springboot 的配置方式来的了,读取的是 application.yml 配置文件中 eureka 的配置了,并且每个配置也提供了默认值。

例如 EurekaServerConfigBean:可以看到前面遇到过的一些参数,并且提供了默认值

@ConfigurationProperties(EurekaServerConfigBean.PREFIX)
public class EurekaServerConfigBean implements EurekaServerConfig {
    /**
     * Eureka server configuration properties prefix.
     */
    public static final String PREFIX = "eureka.server";
    private static final int MINUTES = 60 * 1000;
    private boolean enableSelfPreservation = true;
    private double renewalPercentThreshold = 0.85;
    private int renewalThresholdUpdateIntervalMs = 15 * MINUTES;
    private int peerEurekaNodesUpdateIntervalMs = 10 * MINUTES;
    private int numberOfReplicationRetries = 5;
    private int peerEurekaStatusRefreshTimeIntervalMs = 30 * 1000;
    private int waitTimeInMsWhenSyncEmpty = 5 * MINUTES;
    private int peerNodeConnectTimeoutMs = 200;
    private int peerNodeReadTimeoutMs = 200;
    private int peerNodeTotalConnections = 1000;
    private int peerNodeTotalConnectionsPerHost = 500;
    private int peerNodeConnectionIdleTimeoutSeconds = 30;
    private long retentionTimeInMSInDeltaQueue = 3 * MINUTES;
    private long deltaRetentionTimerIntervalInMs = 30 * 1000;
    private long evictionIntervalTimerInMs = 60 * 1000;
    private long responseCacheAutoExpirationInSeconds = 180;
    private long responseCacheUpdateIntervalMs = 30 * 1000;
    private boolean useReadOnlyResponseCache = true;
    private boolean disableDelta;
    private int registrySyncRetries = 0;
    private int initialCapacityOfResponseCache = 1000;
    private int expectedClientRenewalIntervalSeconds = 30;
    private String myUrl;
    
    //....

    @Override
    public boolean shouldEnableSelfPreservation() {
        return this.enableSelfPreservation;
    }

    @Override
    public boolean shouldDisableDelta() {
        return this.disableDelta;
    }

    @Override
    public boolean shouldUseReadOnlyResponseCache() {
        return this.useReadOnlyResponseCache;
    }

    public boolean isEnableSelfPreservation() {
        return enableSelfPreservation;
    }

    @Override
    public double getRenewalPercentThreshold() {
        return renewalPercentThreshold;
    }

    //...
}

3、Eureka Server 初始化

还可以看到 EurekaServerAutoConfiguration 导入了 EurekaServerInitializerConfiguration 的初始化配置类,它启动了一个后台线程来初始化 eurekaServerBootstrap,进入可以看到跟 EurekaBootStrap 的初始化是类似的,只不过是简化了些,就不在展示了。

public void start() {
    new Thread(() -> {
        try {
            // TODO: is this class even needed now?
            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();
}

spring-cloud-starter-netflix-eureka-client

1、Eureka Client 自动化配置

Eureka Client 自动化配置类是 EurekaClientAutoConfiguration(@EnableEurekaClient 注解感觉没啥用),这里初始化类里主要初始化了 ApplicationInfoManager、EurekaClientConfigBean、EurekaInstanceConfigBean、EurekaClient 等。

需要注意,在 springcloud 中,EurekaClient 组件默认是 CloudEurekaClient

@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties
@ConditionalOnClass(EurekaClientConfig.class)
@ConditionalOnProperty(value = "eureka.client.enabled", matchIfMissing = true)
@ConditionalOnDiscoveryEnabled
@AutoConfigureBefore({ NoopDiscoveryClientAutoConfiguration.class, CommonsClientAutoConfiguration.class, ServiceRegistryAutoConfiguration.class })
@AutoConfigureAfter(name = {
        "org.springframework.cloud.netflix.eureka.config.DiscoveryClientOptionalArgsConfiguration",
        "org.springframework.cloud.autoconfigure.RefreshAutoConfiguration",
        "org.springframework.cloud.netflix.eureka.EurekaDiscoveryClientConfiguration",
        "org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationAutoConfiguration" })
public class EurekaClientAutoConfiguration {
    private ConfigurableEnvironment env;
    public EurekaClientAutoConfiguration(ConfigurableEnvironment env) {
        this.env = env;
    }

    @Bean
    @ConditionalOnMissingBean(value = EurekaClientConfig.class, search = SearchStrategy.CURRENT)
    public EurekaClientConfigBean eurekaClientConfigBean(ConfigurableEnvironment env) {
        return new EurekaClientConfigBean();
    }

    @Bean
    @ConditionalOnMissingBean
    public ManagementMetadataProvider serviceManagementMetadataProvider() {
        return new DefaultManagementMetadataProvider();
    }

    @Bean
    @ConditionalOnMissingBean(value = EurekaInstanceConfig.class, search = SearchStrategy.CURRENT)
    public EurekaInstanceConfigBean eurekaInstanceConfigBean(InetUtils inetUtils,
            ManagementMetadataProvider managementMetadataProvider) {
        //....
        return instance;
    }

    @Bean
    public EurekaServiceRegistry eurekaServiceRegistry() {
        return new EurekaServiceRegistry();
    }

    @Configuration(proxyBeanMethods = false)
    @ConditionalOnMissingRefreshScope
    protected static class EurekaClientConfiguration {
        @Autowired
        private ApplicationContext context;
        @Autowired
        private AbstractDiscoveryClientOptionalArgs<?> optionalArgs;

        @Bean(destroyMethod = "shutdown")
        @ConditionalOnMissingBean(value = EurekaClient.class, search = SearchStrategy.CURRENT)
        public EurekaClient eurekaClient(ApplicationInfoManager manager, EurekaClientConfig config) {
        	// EurekaClient 实际类型为 CloudEurekaClient
            return new CloudEurekaClient(manager, config, this.optionalArgs, this.context);
        }

        @Bean
        @ConditionalOnMissingBean(value = ApplicationInfoManager.class, search = SearchStrategy.CURRENT)
        public ApplicationInfoManager eurekaApplicationInfoManager(EurekaInstanceConfig config) {
            InstanceInfo instanceInfo = new InstanceInfoFactory().create(config);
            return new ApplicationInfoManager(config, instanceInfo);
        }
    }

    //...
}

2、Eureka Client 注册

Netflix 中服务注册的逻辑是在 InstanceInfoReplicator,springcloud 则封装到了 EurekaAutoServiceRegistration,InstanceInfoReplicator 启动之后要延迟40秒才会注册到注册中心,而这里的自动化配置在服务启动时就会注册到注册中心。

它这里调用了 serviceRegistry 来注册,进去可以发现它就是调用了 ApplicationInfoManager 的 setInstanceStatus 方法,进而触发了那个状态变更器 StatusChangeListener,然后向注册中心注册。

public void start() {
    // only initialize if nonSecurePort is greater than 0 and it isn't already running
    // because of containerPortInitializer below
    if (!this.running.get() && this.registration.getNonSecurePort() > 0) {
		// 注册
        this.serviceRegistry.register(this.registration);

        this.context.publishEvent(new InstanceRegisteredEvent<>(this, this.registration.getInstanceConfig()));
        this.running.set(true);
    }
}

Eureka 总结

这一节来对 eureka 的学习做个总结,注意下面的一些截图是来自《重新定义Spring Cloud实战》,具体可以参考原著。

Eureka Server 提供的 API

大部分的 API 在前面的源码分析中都已经接触过了,这里看下 eureka 提供的 API列表。注意 eureka 整合到 springcloud 之后,api 前缀固定为 /eureka,在 netflix 中是 /{version} 的形式。

Eureka Client 核心参数

Eureka Client 的参数可以分为基本参数、定时任务参数、http参数三大类。

1、基本参数

2、定时任务参数

3、http 参数

Eureka Server 核心参数

Eureka Server 的参数可以分为基本参数、多级缓存参数、集群相关参数、http参数四大类。

1、基本参数

2、多级缓存参数

3、集群参数

4、http 参数

Eureka 核心功能

1、服务注册和发现:eureka 分客户端(Eureka Client)和服务端(Eureka Server),服务端即为注册中心,提供服务注册和发现的功能。所有客户端将自己注册到注册中心上,服务端使用 Map 结构基于内存保存所有客户端信息(IP、端口、续约等信息)。客户端定时从注册中心拉取注册表到本地,就可以通过负载均衡的方式进行服务间的调用。

2、服务注册(Register):Eureka Client 启动时向 Eureka Server 注册,并提供自身的元数据、IP地址、端口、状态等信息。

3、服务续约(Renew):Eureka Client 默认每隔30秒向 Eureka Server 发送一次心跳进行服务续约,通过续约告知 Eureka Server 自己是正常的。如果 Eureka Server 180秒没有收到客户端的续约,就会认为客户端故障,并将其剔除。

4、抓取注册表(Fetch Registry):Eureka Client 启动时会向 Eureka Server 全量抓取一次注册表到本地,之后会每隔30秒增量抓取注册表合并到本地注册表。如果合并后的本地注册表与 Eureka Server 端的注册表不一致(hash 比对),就全量抓取注册表覆盖本地的注册表。

5、服务下线(Cancel):Eureka Client 程序正常关闭时,会向 Eureka Server 发送下线请求,之后 Eureka Server 将这个实例从注册表中剔除。

6、故障剔除(Eviction):默认情况下,Eureka Client 连续180秒没有向 Eureka Server 发送续约请求,就会被认为实例故障,然后从注册表剔除。

7、Eureka Server 集群:Eureka Server 采用对等复制模式(Peer to Peer)来进行副本之间的数据同步,集群中每个 Server 节点都可以接收写操作和读操作。Server 节点接收到写操作后(注册、续约、下线、状态更新)会通过后台任务打包成批量任务发送到集群其它 Server 节点进行数据同步。Eureka Server 集群副本之间的数据会有短暂的不一致性,它是满足 CAP 中的 AP,即 高可用性和分区容错性

Eureka 核心类和组件

1、Eureka Server

  • Eureka Server 启动初始化:com.netflix.eureka.EurekaBootStrap
  • 服务端配置:com.netflix.eureka.EurekaServerConfig
  • 序列化器:com.netflix.eureka.resources.ServerCodecs
  • 实例注册:com.netflix.eureka.registry.InstanceRegistry
  • 续约管理:com.netflix.eureka.lease.LeaseManager
  • 发现服务:com.netflix.discovery.shared.LookupService
  • 感知集群的注册表:com.netflix.eureka.registry.PeerAwareInstanceRegistry
  • Eureka Server 集群:com.netflix.eureka.cluster.PeerEurekaNodes
  • Eureka Server 集群节点:com.netflix.eureka.cluster.PeerEurekaNode
  • Eureka Server 上下文:com.netflix.eureka.EurekaServerContext
  • Eureka 监控统计:com.netflix.eureka.util.EurekaMonitors
  • 多级缓存组件:com.netflix.eureka.registry.ResponseCache
  • 资源入口:ApplicationsResource、ApplicationResource、InstancesResource、InstanceResource、PeerReplicationResource

注册表类结构:

2、Eureka Client

  • 应用实例配置:com.netflix.appinfo.EurekaInstanceConfig
  • 客户端配置:com.netflix.discovery.EurekaClientConfig
  • 网络传输配置:com.netflix.discovery.shared.transport.EurekaTransportConfig
  • 实例信息:com.netflix.appinfo.providers.InstanceInfo
  • 应用信息管理器:com.netflix.appinfo.ApplicationInfoManager
  • Eureka 客户端:com.netflix.discovery.EurekaClient(com.netflix.discovery.DiscoveryClient)
  • Eureka 客户端与服务端通信的底层组件:com.netflix.discovery.shared.transport.EurekaHttpClient
  • 实例注册器:com.netflix.discovery.InstanceInfoReplicator
  • 状态变更监听器:com.netflix.appinfo.ApplicationInfoManager.StatusChangeListener
  • 应用管理器:com.netflix.discovery.shared.Applications
  • 应用:com.netflix.discovery.shared.Application

3、集群同步:

  • 复制任务处理器:com.netflix.eureka.cluster.ReplicationTaskProcessor
  • 批量分发器:com.netflix.eureka.util.batcher.TaskDispatcher
  • 接收者执行器:com.netflix.eureka.util.batcher.AcceptorExecutor
  • 任务处理器:com.netflix.eureka.util.batcher.TaskExecutors

Eureka 后台任务

Eureka 后台大量用到了定时任务来保证服务实例的注册和发现,这节看下 eureka 都有哪些地方用到了定时任务。

1、Eureka Client

  • DiscoveryClient:CacheRefreshThread,定时刷新注册表,30秒执行一次,定时抓取增量注册表到本地
  • DiscoveryClient:HeartbeatThread,定时发送心跳,30秒执行一次,向 Eureka Server 发送续约请求
  • DiscoveryClient:InstanceInfoReplicator,实例复制器,30秒执行一次,如果实例信息变更,则向 Eureka Server 重新注册

2、Eureka Server

  • AbstractInstanceRegistry:DeltaRetentionTask,30秒执行一次,将最近变更队列 recentlyChangedQueue 中超过 180 秒的实例从队列中移除
  • ResponseCacheImpl:LoadingCacheExpire,读写缓存 readWriteCacheMap 中的数据每隔 180 秒失效,读取时重新从注册表加载新的数据
  • ResponseCacheImpl:CacheUpdateTask,每隔30秒将读写缓存 readWriteCacheMap 的数据同步到只读缓存 readOnlyCacheMap 中
  • PeerAwareInstanceRegistryImpl:RenewalThresholdUpdateTask,每隔15分钟更新每分钟续约阈值 numberOfRenewsPerMinThreshold
  • PeerEurekaNodes:PeersUpdateTask,每隔10分钟更新集群节点信息
  • AbstractInstanceRegistry:EvictionTask,每隔60秒执行一次,定时剔除故障(超过180秒未续约)的实例
  • AcceptorExecutor:AcceptorRunner,后台循环运行,将 acceptorQueue 和 reprocessQueue 队列的任务转移到 processingOrder,然后每隔500毫秒将任务打包成一个批次到 batchWorkQueue
  • TaskExecutors:BatchWorkerRunnable,将 batchWorkQueue 的批量任务发送到集群节点
  • TaskExecutors:SingleTaskWorkerRunnable,将 singleItemWorkQueue 的单项任务发送到集群节点

Eureka 的一些优秀设计

1、Eureka 注册中心

Eureka 作为注册中心整体的运行机制,服务注册、抓取注册表、续约、下线、定时剔除故障实例等一整套机制都是值得学习的。

2、基于接口的配置读取方式

eureka 将客户端配置、实例配置、服务端配置的读取分别定义在三个接口类中,并提供了默认实现类,在实现类中给了默认值。这种基于接口的配置读取方式也是可以借鉴的。可以看到,Springcloud 集成 eureka 时就自定义了三个配置接口的实现类,并基于 springboot 的方式从 application.yml 文件中读取配置。

3、定时任务监管器 TimedSupervisorTask

  • 首先在远程调用的时候要考虑到网络不可用、server 端 down 了等情况导致调用超时,可以使用线程池异步提交任务,实现等待超时机制。
  • 超时之后,可以假想服务恢复可用状态可能需要一定的时间,如果还是按原来的时间间隔调度,可能还是会超时,因此增大延迟时间。如果调用成功,说明已经恢复了,则重置延迟时间。
  • 定时任务的调度以一定的延迟时间来循环调度(schedule),延迟时间可以根据实际情况变化,而不是一开始就按一个固定的频率来调度(scheduleAtFixedRate)。

4、最近一分钟计数器 MeasuredRate

MeasuredRate 利用两个桶来计数,一个保存上一间隔时间的计数,一个保存当前这一间隔时间的计数,然后使用定时任务每隔一定间隔时间就将当前这个桶的计数替换到上一个桶里。然后增加计数的时候增加当前桶,获取数量的时候从上一个桶里获取,就实现了获取上一个间隔时间的计数。

5、定时任务补偿时间

eureka 后台用到了大量的定时任务,例如每隔30秒运行一次、每隔60秒运行一次,但是如果因为GC停顿、本地时间漂移等问题,就会导致每次任务的间隔时间不一致,因此 eureka 会判断两次任务的间隔时间与定时间隔时间,得到一个补偿时间,例如定时摘除过期实例的任务 EvictionTask。

有些任务还可能因为网络超时、阻塞等原因导致任务失败,eureka 就会认为远程服务可能暂时不可用,就会延迟一定时间再调度,避免频繁失败。例如 TimedSupervisorTask 的设计,后台发送批量任务到集群节点的任务等。

6、并发队列的应用

Eureka 为了保证高性能,所有数据都是保存在内存中的,为了保证共享数据的并发安全,它大量使用了JDK并发包下的原子类(AtomicLong、AtomicReference)、并发队列(LinkedBlockingQueue、ConcurrentLinkedQueue)、并发容器(ConcurrentHashMap)、并发工具(Semaphore)等。

7、三级缓存高性能读取

抓取注册表时的三级缓存结构设计,读取数据先从只读缓存读取,只读缓存没有再从读写缓存读,读写缓存没有最后再从注册表读。缓存更新的机制则是,注册、下线都会失效读写缓存,读写缓存每隔180秒过期,读写缓存每隔30秒同步到只读缓存。

8、增量数据更新

定时更新注册表时,采用的是增量更新,而增量更新的数据是用一个最近变更队列保存了最近三分钟变更的实例。注册、下线等操作都会将实例放入到这个最近变更队列,然后定时任务将队列中超过180秒的实例移除。

9、数据副本同步hash一致性比对

更新注册表时,合并到本地后,采用了 hash 一致性比对的方式来保证数据同步的正确性。在分布式系统中,数据同步我们也可以采用这个思路,先增量获取数据,服务端返回一个全量数据的 hash 值,客户端合并数据后,计算一个本地的 hash 值,如果 hash 值不一致,说明数据缺失,就进行一次全量更新数据,来保证数据的一致性。

10、自我保护机制

如果客户端超过180秒未续约则被认为是实例故障,后台定时任务会定时清除故障的实例。但 eureka 并不是直接把所有过期实例都清除掉,它会判断最近一分钟客户端续约次数是否大于每分钟续约阈值(85%),如果低于这个阈值,就任务是自身网络抖动导致客户端无法续约,然后进入自我保护模式,不再剔除过期实例。而且,在摘除过期实例的时候,它也不是一次性摘除所有过期实例,而是一次只摘除不超过15%的实例,分批次摘除。

eureka 认为保留可用及过期的数据总比丢失掉可用的数据好。我觉得它这里的一套自我保护机制的思想是值得我们学习的。

11、监控统计

各种操作都会进行统计,比如注册、续约、下线、抓取注册表、集群同步、实例过期等,可以看下 EurekaMonitors 这个类。在开发一些系统时,我们也应该做好统计,便于分析问题。

12、Eureka Server 集群

Eureka 集群采用 Peer to Peer 的对等复制模式,每个节点都可以写入数据,然后通过多层任务队列+批量处理的机制进行集群间数据同步。同时后台定时更新集群节点信息,保证集群高可用。

Eureka 集群是保证CAP中的 AP,保证高可用及分区容错性,副本数据则是最终一致。集群数据同步难免可能会失败、延迟等导致数据不一致,eureka 采用最后更新时间比对以及续约的机制来进行数据的修正,保证集群数据同步的最终一致性。

Eureka Server 承载高并发访问压力

1、Eureka Server 的访问压力有多大

首先来计算下一个大型系统会对 Eureka Server 产生多大的访问压力。例如有一个微服务系统,有 100 个服务,每个服务部署 10 个实例。

每个实例每隔30秒向 Eureka Server 发送一次心跳以及拉取注册表,这是 Eureka Server 的主要访问压力,那么每分钟 Eureka Server 就会接收到 4 * 100 * 10 = 4000 次请求,每秒 4000 / 60 ≈ 67 次请求。Eureka Server 还会处理集群同步、接收注册、下线、抓取全量注册表的一些额外请求,就估算每秒接收个100次请求吧。这样每天算下来就是 100 * 60 * 60 * 24 = 864 万次请求,也就是每天接近千万级别的访问量。

所以各服务实例每隔30秒抓取增量注册表,以及每隔30秒发送心跳给Eureka Server,其实这个时间频率设置是有其用意的,一般我们也不用修改这两个参数

另外 Eureka Server 每秒接收100次请求,如果一台机器每秒能扛40次请求,那 Eureka Server 集群就可以部署3个实例。我们就可以以此来估算 Eureka Server 集群部署的实例数。

2、Eureka Server 如何抗住每秒百次请求的

Eureka Server 是基于纯内存的 CocurrentHashMap 结构来保存注册表的,服务注册、续约、下线、抓去注册表都是操作这个内存的注册表,这是 Eureka Server 能抗住高并发的一个核心点。

除此之外,Eureka Server 还设计了多级缓存机制来提高并发能力。因为 Eureka Server 是基于纯内存的数据结构来保存注册表的,如果所有的读和写操作都直接操作这个Map,那并发性能就非常低。所以多级缓存的设计能避免同时读写内存数据结构造成的并发冲突问题,能够进一步提升服务请求的响应速度。

一张图总结 Eureka 整体架构

最后用一张图来总结下 Eureka 的整体架构、运行流程以及核心机制。