• SpringCloud 源码系列(3)—— 注册中心 Eureka(下)


    SpringCloud 源码系列(1)—— 注册中心 Eureka(上)

    SpringCloud 源码系列(2)—— 注册中心 Eureka(中)

    SpringCloud 源码系列(3)—— 注册中心 Eureka(下)

    十一、Eureka Server 集群

    在实际的生产环境中,可能有几十个或者几百个的微服务实例,Eureka Server 承担了非常高的负载,而且为了保证注册中心高可用,一般都要部署成集群的,下面就来看看 eureka server 的集群。

    1、搭建 Eureka Server 集群

    首先来搭建一个三个节点的 eureka-server 集群,看看效果。

    ① 集群配置

    首先在本地 hosts 文件中配置如下映射:

    1 127.0.0.1 peer1
    2 127.0.0.1 peer2
    3 127.0.0.1 peer3

    更改注册中心的 application.yml 配置文件,增加三个 profile,分别对应三个 eureka-server 的客户端配置。

    eureka-server 在集群中作为客户端就需要抓取注册表,并配置 eureka-server 的地址。

     1 spring:
     2   application:
     3     name: sunny-register
     4 
     5 ---
     6 spring:
     7   profiles: peer1
     8 server:
     9   port: 8001
    10 
    11 eureka:
    12   instance:
    13     hostname: peer1
    14   client:
    15     # 是否向注册中心注册自己
    16     register-with-eureka: false
    17     # 是否抓取注册表
    18     fetch-registry: true
    19     service-url:
    20       defaultZone: http://peer1:8001/eureka,http://peer2:8002/eureka,http://peer3:8003/eureka
    21 
    22 
    23 ---
    24 spring:
    25   profiles: peer2
    26 server:
    27   port: 8002
    28 
    29 eureka:
    30   instance:
    31     hostname: peer2
    32   client:
    33     # 是否向注册中心注册自己
    34     register-with-eureka: false
    35     # 是否抓取注册表
    36     fetch-registry: true
    37     service-url:
    38       defaultZone: http://peer1:8001/eureka,http://peer2:8002/eureka,http://peer3:8003/eureka
    39 
    40 ---
    41 spring:
    42   profiles: peer3
    43 server:
    44   port: 8003
    45 
    46 eureka:
    47   instance:
    48     hostname: peer3
    49   client:
    50     # 是否向注册中心注册自己
    51     register-with-eureka: false
    52     # 是否抓取注册表
    53     fetch-registry: true
    54     service-url:
    55       defaultZone: http://peer1:8001/eureka,http://peer2:8002/eureka,http://peer3:8003/eureka

    ② 启动集群

    分别启动三个注册中心,环境变量 spring.profiles.active 激活对应的集群配置。

    启动之后访问 http://peer1:8001/ 进入 peer1 这个注册中心,就可以看到另外两个分片 peer2、peer3,说明集群中有3个节点了。

    ③ 启动客户端

    首先客户端配置增加集群地址:

    1 eureka:
    2   client:
    3     serviceUrl:
    4       defaultZone: http://peer1:8001/eureka,http://peer2:8002/eureka,http://peer3:8003/eureka

    启动几个客户端实例,过一会 之后,会发现三个 eureka-server 上都注册上去了:

    到此 eureka-server 集群就搭建起来了,可以看到注册中心的实例会互相同步,每隔注册注册都可以接收注册、续约、下线等请求,它们是对等的。

    2、Eureka Server 集群架构

    一般来说,分布式系统的数据在多个副本之间的复制方式,可分为主从复制和对等复制。

    ① 主从复制

    主从复制就是 Master-Slave 模式,即一个主副本,其它副本都为从副本。所有对数据的写操作都提交到主副本,然后再由主副本同步到从副本。

    对于主从复制模式来说,写操作的压力都在主副本上,它是整个系统的瓶颈,而从副本则可以帮助主副本分担读请求。

    ② 对等复制

    对等复制就是 Peer to Peer 的模式,副本之间不分主从,任何副本都可以接收写操作,每个副本之间相互进行数据更新同步。

    Peer to Peer 模式每个副本之间都可以接收写请求,不存在写操作压力瓶颈。但是由于每个副本都可以进行写操作,各个副本之间的数据同步及冲突处理是一个棘手的问题。

    ③ Eureka Server 集群架构

    Eureka Server 采用的就是 Peer to Peer 的复制模式,比如一个客户端实例随机向其中一个server注册,然后它就会同步到其它节点中。

    3、Eureka Server 启动时抓取注册表

    前面已经分析过了,在 eureka server 启动初始化的时候,即 EurekaBootStrap 初始化类,先初始化了 DiscoveryClient,DiscoveryClient 会向注册中心全量抓取注册表到本地。

    初始化的最后调用了 registry.syncUp() 来同步注册表,就是将 DiscoveryClient 缓存的实例注册到 eureka-server 的注册表里去。

    需要注意的是 eureka 配置的注册表同步重试次数默认为5,springcloud 中默认为 0,因此需要添加如下配置来开启注册表同步。

    1 eureka:
    2   server:
    3     registry-sync-retries: 5

    将 DiscoveryClient 本地的实例注册到注册表中:

    4、集群节点同步

    ① 注册、续约、下线

    前面也分析过了,在客户端注册、续约、下线的时候,都会同步到集群其它节点。可以看到都调用了 replicateToPeers 方法来复制到其它集群。

     1 /////////////////////// 注册 ///////////////////////
     2 public void register(final InstanceInfo info, final boolean isReplication) {
     3     int leaseDuration = Lease.DEFAULT_DURATION_IN_SECS;
     4     // 如果实例中没有周期的配置,就设置为默认的 90 秒
     5     if (info.getLeaseInfo() != null && info.getLeaseInfo().getDurationInSecs() > 0) {
     6         leaseDuration = info.getLeaseInfo().getDurationInSecs();
     7     }
     8     // 注册实例
     9     super.register(info, leaseDuration, isReplication);
    10     // 复制到集群其它 server 节点
    11     replicateToPeers(Action.Register, info.getAppName(), info.getId(), info, null, isReplication);
    12 }
    13 
    14 
    15 /////////////////////// 下线 ///////////////////////
    16 public boolean cancel(final String appName, final String id,
    17                       final boolean isReplication) {
    18     if (super.cancel(appName, id, isReplication)) {
    19         replicateToPeers(Action.Cancel, appName, id, null, null, isReplication);
    20 
    21         return true;
    22     }
    23     return false;
    24 }
    25 
    26 
    27 /////////////////////// 续约 ///////////////////////
    28 public boolean renew(final String appName, final String id, final boolean isReplication) {
    29     // 调用父类(AbstractInstanceRegistry)的 renew 续约
    30     if (super.renew(appName, id, isReplication)) {
    31         // 续约完成后同步到集群其它节点
    32         replicateToPeers(Action.Heartbeat, appName, id, null, null, isReplication);
    33         return true;
    34     }
    35     return false;
    36 }

    ② 同步到其它节点

    来看看 replicateToPeers 方法:

    • 首先判断 isReplication 参数,如果是集群复制操作,最近一分钟复制次数 numberOfReplicationsLastMin + 1。isReplication 是在请求头中指定的,请求头为 PeerEurekaNode.HEADER_REPLICATION(x-netflix-discovery-replication)。
    • 接着遍历集群列表,复制实例操作到集群节点中。前面也分析过了,PeerEurekaNode 就代表了一个 eureka-server,PeerEurekaNodes 就代表了 eureka-server 集群。
    • 复制实例操作到集群的方法 replicateInstanceActionsToPeers 就是根据不同的操作类型调用集群 PeerEurekaNode 对应的方法完成操作复制。
     1 private void replicateToPeers(Action action, String appName, String id,
     2                               InstanceInfo info /* optional */,
     3                               InstanceStatus newStatus /* optional */, boolean isReplication) {
     4     Stopwatch tracer = action.getTimer().start();
     5     try {
     6         if (isReplication) {
     7             // 如果是来自其它server节点的注册请求,则最近一分钟集群同步次数+1
     8             numberOfReplicationsLastMin.increment();
     9         }
    10         // If it is a replication already, do not replicate again as this will create a poison replication
    11         if (peerEurekaNodes == Collections.EMPTY_LIST || isReplication) {
    12             return;
    13         }
    14 
    15         // 如果是来自客户端的注册请求,就同步到集群中其它server节点
    16         for (final PeerEurekaNode node : peerEurekaNodes.getPeerEurekaNodes()) {
    17             // If the url represents this host, do not replicate to yourself.
    18             if (peerEurekaNodes.isThisMyUrl(node.getServiceUrl())) {
    19                 continue;
    20             }
    21 
    22             replicateInstanceActionsToPeers(action, appName, id, info, newStatus, node);
    23         }
    24     } finally {
    25         tracer.stop();
    26     }
    27 }
     1 private void replicateInstanceActionsToPeers(Action action, String appName,
     2                                              String id, InstanceInfo info, InstanceStatus newStatus,
     3                                              PeerEurekaNode node) {
     4     try {
     5         InstanceInfo infoFromRegistry;
     6         CurrentRequestVersion.set(Version.V2);
     7         switch (action) {
     8             case Cancel:
     9                 // 下线
    10                 node.cancel(appName, id);
    11                 break;
    12             case Heartbeat:
    13                 InstanceStatus overriddenStatus = overriddenInstanceStatusMap.get(id);
    14                 infoFromRegistry = getInstanceByAppAndId(appName, id, false);
    15                 // 续约
    16                 node.heartbeat(appName, id, infoFromRegistry, overriddenStatus, false);
    17                 break;
    18             case Register:
    19                 // 注册
    20                 node.register(info);
    21                 break;
    22             case StatusUpdate:
    23                 infoFromRegistry = getInstanceByAppAndId(appName, id, false);
    24                 node.statusUpdate(appName, id, newStatus, infoFromRegistry);
    25                 break;
    26             case DeleteStatusOverride:
    27                 infoFromRegistry = getInstanceByAppAndId(appName, id, false);
    28                 node.deleteStatusOverride(appName, id, infoFromRegistry);
    29                 break;
    30         }
    31     } catch (Throwable t) {
    32         logger.error("Cannot replicate information to {} for action {}", node.getServiceUrl(), action.name(), t);
    33     } finally {
    34         CurrentRequestVersion.remove();
    35     }
    36 }

    ③ isReplication

    PeerEurekaNode 与 eureka-server 通信的组件是 JerseyReplicationClient,这个类重写了 addExtraHeaders 方法,并添加了请求头 PeerEurekaNode.HEADER_REPLICATION,设置为 true。

    这样其它 eureka-server 收到这个复制操作后,就知道是来自集群节点的同步操作,就不会再同步给其它节点了,从而避免死循环。

    1 @Override
    2 protected void addExtraHeaders(Builder webResource) {
    3     webResource.header(PeerEurekaNode.HEADER_REPLICATION, "true");
    4 }

    十二、集群同步机制

    Eureka Server 集群间同步机制还是比较复杂的,试想如果每次客户端的请求一过来,比如注册、心跳,然后 eureka-server 就立马同步给集群中其它 server 节点,那 eureka-server 这种 Peer to Peer 的模式实际上就无法分担客户端的写操作压力,相当于每个 eureka-server 接收到的请求量都是一样的。那 eureka server 为了避免这种情况,底层采用了三层队列,加批量任务的方式来进行集群间的同步。简单来说就是先将客户端操作放入队列中,然后从队列中取出一批操作,然后将这一批操作发送给其它 Server 节点,Server节点接收到之后再将这批操作解析到本地。下面就来详细看看是如何实现的。

    1、集群节点 PeerEurekaNode

    之前分析 eureka-server 启动初始化的时候,EurekaBootStrap 初始化了代表集群的 PeerEurekaNodes,它里面又根据配置的注册中心地址构造了 PeerEurekaNode,集群间同步核心的组件就是这个 PeerEurekaNode 了。下面以客户端注册为例来看下是如何同步的。

    ① 注册同步

    replicateInstanceActionsToPeers 中调用了 PeerEurekaNode 的 register 方法来同步注册操作到集群。

    node.register 方法:

    • 可以看到先计算了过期时间,为当前时间 + 租约间隔时间(默认90秒)
    • 然后调用了 batchingDispatcher 批量任务分发器来处理任务,提交了一个 InstanceReplicationTask 的实例,其 execute 方法中调用了 replicationClient 来向这个 server 注册同步。
     1 public void register(final InstanceInfo info) throws Exception {
     2     // 过期时间:当前时间 + 租约时间(默认90秒)
     3     long expiryTime = System.currentTimeMillis() + getLeaseRenewalOf(info);
     4     batchingDispatcher.process(
     5             taskId("register", info),
     6             new InstanceReplicationTask(targetHost, Action.Register, info, null, true) {
     7                 public EurekaHttpResponse<Void> execute() {
     8                     return replicationClient.register(info);
     9                 }
    10             },
    11             expiryTime
    12     );
    13 }

    再看下 getLeaseRenewalOf 这个方法,这里应该是有bug的,这个方法返回的是毫秒数,可以看到它的卫语句的else部分是乘以 1000 了的,而 if 部分则没有,返回的是 90,不过这里 info.getLeaseInfo() 应该都不会为 null。

    1 private static int getLeaseRenewalOf(InstanceInfo info) {
    2     // bug : Lease.DEFAULT_DURATION_IN_SECS * 1000
    3     return (info.getLeaseInfo() == null ? Lease.DEFAULT_DURATION_IN_SECS : info.getLeaseInfo().getRenewalIntervalInSecs()) * 1000;
    4 }

    ② PeerEurekaNode 的构造

    batchingDispatcher 是在 PeerEurekaNode 的构造方法中初始化的,来看下它的构造方法:

    • registry:本地注册表
    • targetHost:eureka-server host
    • replicationClient:基于 jersey 的集群复制客户端通信组件,它在请求头中设置了 PeerEurekaNode.HEADER_REPLICATION 为 true
    • serviceUrl:eureka-server 地址
    • maxProcessingDelayMs:最大处理延迟毫秒数,默认为30000毫秒,即30秒,在下线的时候有用到
    • batcherName:批处理器名称
    • taskProcessor:复制任务处理器,它封装了 targetHost 和 replicationClient,主要就是 ReplicationTaskProcessor 在处理批量任务的提交
    • batchingDispatcher:批量任务分发器,它会将任务打成一个批次提交到 eureka-server,避免多次请求eureka-server,注册时就是先用这个分发器提交的任务
    • nonBatchingDispatcher:非批量任务分发器,就是一个任务一个任务的提交
     1 public PeerEurekaNode(PeerAwareInstanceRegistry registry, String targetHost, String serviceUrl, HttpReplicationClient replicationClient, EurekaServerConfig config) {
     2     this(registry, targetHost, serviceUrl, replicationClient, config, BATCH_SIZE, MAX_BATCHING_DELAY_MS, RETRY_SLEEP_TIME_MS, SERVER_UNAVAILABLE_SLEEP_TIME_MS);
     3 }
     4 
     5 PeerEurekaNode(PeerAwareInstanceRegistry registry, String targetHost, String serviceUrl,
     6                                  HttpReplicationClient replicationClient, EurekaServerConfig config,
     7                                  int batchSize, long maxBatchingDelayMs,
     8                                  long retrySleepTimeMs, long serverUnavailableSleepTimeMs) {
     9     this.registry = registry;
    10     // 集群节点 host
    11     this.targetHost = targetHost;
    12     this.replicationClient = replicationClient;
    13 
    14     // 集群节点地址
    15     this.serviceUrl = serviceUrl;
    16     this.config = config;
    17     // 最大延迟时间 默认30秒
    18     this.maxProcessingDelayMs = config.getMaxTimeForReplication();
    19 
    20     // 批处理器名称
    21     String batcherName = getBatcherName();
    22 
    23     // 复制任务处理器
    24     ReplicationTaskProcessor taskProcessor = new ReplicationTaskProcessor(targetHost, replicationClient);
    25     // 批量任务分发器
    26     this.batchingDispatcher = TaskDispatchers.createBatchingTaskDispatcher(
    27             batcherName,
    28             // 复制池里最大容量,默认 10000
    29             config.getMaxElementsInPeerReplicationPool(),
    30             batchSize, // 250
    31             // 同步使用的最大线程数 默认 20
    32             config.getMaxThreadsForPeerReplication(),
    33             maxBatchingDelayMs, // 500
    34             serverUnavailableSleepTimeMs, // 1000
    35             retrySleepTimeMs, // 100
    36             taskProcessor
    37     );
    38     // 单个任务分发器
    39     this.nonBatchingDispatcher = TaskDispatchers.createNonBatchingTaskDispatcher(
    40             targetHost,
    41             config.getMaxElementsInStatusReplicationPool(),
    42             config.getMaxThreadsForStatusReplication(),
    43             maxBatchingDelayMs,
    44             serverUnavailableSleepTimeMs,
    45             retrySleepTimeMs,
    46             taskProcessor
    47     );
    48 }

    2、批量分发器 TaskDispatcher

    创建 batchingDispatcher 时调用了 TaskDispatchers.createBatchingTaskDispatcher 方法创建了 batchingDispatcher。

    首先看下 createBatchingTaskDispatcher 的参数及默认值,后面分析代码的时候会用到这些参数:

    • id:批量分发器的名称
    • maxBufferSize:缓存池最大数量,默认 10000
    • workloadSize:工作负载数量,即一个批次最多多少任务,默认 250
    • workerCount:工作者数量,这个是线程池线程工作线程的数量,默认20
    • maxBatchingDelay:批量任务最大延迟毫秒数,默认为 500 毫秒
    • congestionRetryDelayMs:阻塞重试延迟毫秒数,默认为 1000 毫秒
    • networkFailureRetryMs:网络失败重试延迟毫秒数,默认为 100 毫秒
    • taskProcessor:任务处理器,即 ReplicationTaskProcessor

    再看下这个方法:

    • 首先创建了一个接收者执行器 AcceptorExecutor,主要的参数是缓存、时间相关的
    • 再创建了一个任务处理器 TaskExecutors,主要的参数是工作线程数、任务处理器以及接收者执行器,可以猜测这应该就是最终执行批量任务提交的执行器
    • 最后创建了任务分发器 TaskDispatcher,从它的 process 方法可以看出,分发器提交的任务实际上又提交给了 AcceptorExecutor

    从这里可以知道,前面注册时 batchingDispatcher.process() 提交的任务其实就是分发到 acceptorExecutor 这个接收者执行器了。创建的这个分发器 TaskDispatcher 主要有接收者执行器 AcceptorExecutor 和 任务处理器 TaskExecutors 这两个组件,核心的分发功能就在这两个组件中。

     1 public static <ID, T> TaskDispatcher<ID, T> createBatchingTaskDispatcher(String id, int maxBufferSize, int workloadSize,
     2                                                                          int workerCount, long maxBatchingDelay, long congestionRetryDelayMs,
     3                                                                          long networkFailureRetryMs, TaskProcessor<T> taskProcessor) {
     4     // 接收者执行器 AcceptorExecutor
     5     final AcceptorExecutor<ID, T> acceptorExecutor = new AcceptorExecutor<>(
     6             id, maxBufferSize, workloadSize, maxBatchingDelay, congestionRetryDelayMs, networkFailureRetryMs
     7     );
     8 
     9     // 任务处理器 TaskExecutors, workerCount = 20
    10     final TaskExecutors<ID, T> taskExecutor = TaskExecutors.batchExecutors(id, workerCount, taskProcessor, acceptorExecutor);
    11 
    12     return new TaskDispatcher<ID, T>() {
    13         @Override
    14         public void process(ID id, T task, long expiryTime) {
    15             // 任务由 acceptorExecutor 处理
    16             acceptorExecutor.process(id, task, expiryTime);
    17         }
    18 
    19         @Override
    20         public void shutdown() {
    21             acceptorExecutor.shutdown();
    22             taskExecutor.shutdown();
    23         }
    24     };
    25 }

    3、接收者执行器 AcceptorExecutor

    先看下创建 AcceptorExecutor 的构造方法:

    • 根据 congestionRetryDelayMs、networkFailureRetryMs 创建了一个时间调整器 TrafficShaper,应该主要就是用来调整补偿时间的
    • 然后创建了一个后台线程 acceptorThread,它运行的任务是 AcceptorRunner,主要就是将任务转成批量任务的
    • 最后就是注册了一些监控统计之类的
     1 AcceptorExecutor(String id,
     2                  int maxBufferSize,
     3                  int maxBatchingSize,
     4                  long maxBatchingDelay,
     5                  long congestionRetryDelayMs,
     6                  long networkFailureRetryMs) {
     7     // 批处理器名称
     8     this.id = id;
     9     // 最大缓冲数:10000
    10     this.maxBufferSize = maxBufferSize;
    11     // 每批最大数量:250
    12     this.maxBatchingSize = maxBatchingSize;
    13     // 最大延迟时间:500 ms
    14     this.maxBatchingDelay = maxBatchingDelay;
    15     // 时间调整器
    16     // congestionRetryDelayMs 阻塞重试延迟时间,1000ms
    17     // networkFailureRetryMs 网络异常重试时间,100ms
    18     this.trafficShaper = new TrafficShaper(congestionRetryDelayMs, networkFailureRetryMs);
    19 
    20     // 接收者后台处理线程
    21     ThreadGroup threadGroup = new ThreadGroup("eurekaTaskExecutors");
    22     this.acceptorThread = new Thread(threadGroup, new AcceptorRunner(), "TaskAcceptor-" + id);
    23     this.acceptorThread.setDaemon(true);
    24     this.acceptorThread.start();
    25 
    26     // 监控统计相关
    27     final double[] percentiles = {50.0, 95.0, 99.0, 99.5};
    28     final StatsConfig statsConfig = new StatsConfig.Builder()
    29             .withSampleSize(1000)
    30             .withPercentiles(percentiles)
    31             .withPublishStdDev(true)
    32             .build();
    33     final MonitorConfig config = MonitorConfig.builder(METRIC_REPLICATION_PREFIX + "batchSize").build();
    34     this.batchSizeMetric = new StatsTimer(config, statsConfig);
    35     try {
    36         Monitors.registerObject(id, this);
    37     } catch (Throwable e) {
    38         logger.warn("Cannot register servo monitor for this object", e);
    39     }
    40 }

    然后看看 AcceptorExecutor 的属性,它定义了几个队列以及容器来处理批量任务,我们先知道有这些东西,后面再来看看都怎么使用的。

    然后可以看到 AcceptorExecutor 大量使用了并发包下的一些类,以及队列的特性,这里我们需要了解下这些类的特性:

    • LinkedBlockingQueue:基于链表的单端阻塞队列,就是队尾入队,队首出队
    • Deque:双端队列,就是队首、队尾都可以入队、出队
    • Semaphore:信号量,需要通过 acquire(或 tryAcquire) 获取到许可证之后才可以进入临界区,通过 release 释放许可证。只要能拿到许可证,Semaphore 是可以允许多个线程进入临界区的。另外注意它这里设置的许可证数量是0,说明要先调用了 release 放入一个许可证,才有可能调用 acquire 获取到许可证。
     1 // 接收任务的队列
     2 private final BlockingQueue<TaskHolder<ID, T>> acceptorQueue = new LinkedBlockingQueue<>();
     3 // 重试任务的队列
     4 private final BlockingDeque<TaskHolder<ID, T>> reprocessQueue = new LinkedBlockingDeque<>();
     5 // 后台接收者线程
     6 private final Thread acceptorThread;
     7 // 待处理任务容器
     8 private final Map<ID, TaskHolder<ID, T>> pendingTasks = new HashMap<>();
     9 // 处理中的队列
    10 private final Deque<ID> processingOrder = new LinkedList<>();
    11 
    12 // 单项队列请求的信号量
    13 private final Semaphore singleItemWorkRequests = new Semaphore(0);
    14 // 单项任务队列
    15 private final BlockingQueue<TaskHolder<ID, T>> singleItemWorkQueue = new LinkedBlockingQueue<>();
    16 
    17 // 批量队列请求的信号量
    18 private final Semaphore batchWorkRequests = new Semaphore(0);
    19 // 批量任务队列
    20 private final BlockingQueue<List<TaskHolder<ID, T>>> batchWorkQueue = new LinkedBlockingQueue<>();
    21 // 时间调整器
    22 private final TrafficShaper trafficShaper;

    TaskDispatcher 调用 acceptorExecutor.process 将任务转给 AcceptorExecutor,可以看到就是将任务添加到接收者队列 acceptorQueue 的队尾了。

    1 void process(ID id, T task, long expiryTime) {
    2     acceptorQueue.add(new TaskHolder<ID, T>(id, task, expiryTime));
    3     acceptedTasks++;
    4 }

    4、接收者任务 AcceptorRunner

    任务添加到 acceptorQueue 了,那任务在哪处理的呢?这就是在 AcceptorRunner 这个任务里去处理的了,这个任务比较复杂,我先把整个代码放出来,再来分析。

      1 class AcceptorRunner implements Runnable {
      2     @Override
      3     public void run() {
      4         long scheduleTime = 0;
      5         while (!isShutdown.get()) {
      6             try {
      7                 // 排出输入队列的任务:将 reprocessQueue、acceptorQueue 队列的任务转移到 pendingTasks
      8                 drainInputQueues();
      9 
     10                 // 待处理的数量
     11                 int totalItems = processingOrder.size();
     12 
     13                 long now = System.currentTimeMillis();
     14                 if (scheduleTime < now) {
     15                     // 时间补偿,正常情况下 transmissionDelay() 返回 0
     16                     scheduleTime = now + trafficShaper.transmissionDelay();
     17                 }
     18                 if (scheduleTime <= now) {
     19                     // 分配批量工作任务:将 pendingTasks 的任务分一批到(最多250个) batchWorkQueue 队列中
     20                     assignBatchWork();
     21                     // 分配单项工作任务:pendingTasks 如果还有剩余任务,将没有过期的转移到 singleItemWorkQueue 队列中
     22                     assignSingleItemWork();
     23                 }
     24 
     25                 // If no worker is requesting data or there is a delay injected by the traffic shaper,
     26                 // sleep for some time to avoid tight loop.
     27                 if (totalItems == processingOrder.size()) {
     28                     Thread.sleep(10);
     29                 }
     30             } catch (InterruptedException ex) {
     31                 // Ignore
     32             } catch (Throwable e) {
     33                 // Safe-guard, so we never exit this loop in an uncontrolled way.
     34                 logger.warn("Discovery AcceptorThread error", e);
     35             }
     36         }
     37     }
     38 
     39     private boolean isFull() {
     40         // 待处理的任务 >= 10000,也就是说 pendingTasks 最多放 10000 个任务
     41         return pendingTasks.size() >= maxBufferSize;
     42     }
     43 
     44     private void drainInputQueues() throws InterruptedException {
     45         do {
     46             // 排出 reprocessQueue,将 reprocessQueue 队列的任务转移到 pendingTasks
     47             drainReprocessQueue();
     48             // 排出 acceptorQueue,将 acceptorQueue 队列的任务转移到 pendingTasks
     49             drainAcceptorQueue();
     50 
     51             if (isShutdown.get()) {
     52                 break;
     53             }
     54             // If all queues are empty, block for a while on the acceptor queue
     55             if (reprocessQueue.isEmpty() && acceptorQueue.isEmpty() && pendingTasks.isEmpty()) {
     56                 // 等待任务放入 acceptorQueue,等待 10 毫秒
     57                 TaskHolder<ID, T> taskHolder = acceptorQueue.poll(10, TimeUnit.MILLISECONDS);
     58                 if (taskHolder != null) {
     59                     // 放入之后 acceptorQueue、pendingTasks 就不为空了
     60                     appendTaskHolder(taskHolder);
     61                 }
     62             }
     63             // pendingTasks 为空、acceptorQueue 不为空、reprocessQueue不为空时,就会一直循环
     64             // 如果所有任务都处理完了,reprocessQueue、acceptorQueue、pendingTasks 都是空的,
     65             // 这时就会循环等待任务进入 acceptorQueue,每次等待 10 毫秒
     66         } while (!reprocessQueue.isEmpty() || !acceptorQueue.isEmpty() || pendingTasks.isEmpty());
     67     }
     68 
     69     private void drainAcceptorQueue() {
     70         while (!acceptorQueue.isEmpty()) {
     71             // 将 acceptorQueue 的任务转移到 pendingTasks
     72             appendTaskHolder(acceptorQueue.poll());
     73         }
     74     }
     75 
     76     private void drainReprocessQueue() {
     77         long now = System.currentTimeMillis();
     78         while (!reprocessQueue.isEmpty() && !isFull()) {
     79             // 从 reprocessQueue 队尾取出任务
     80             TaskHolder<ID, T> taskHolder = reprocessQueue.pollLast();
     81             ID id = taskHolder.getId();
     82             if (taskHolder.getExpiryTime() <= now) {
     83                 // 任务过期
     84                 expiredTasks++;
     85             } else if (pendingTasks.containsKey(id)) {
     86                 // pendingTasks 已存在
     87                 overriddenTasks++;
     88             } else {
     89                 // 将 reprocessQueue 队列的任务放到 pendingTasks
     90                 pendingTasks.put(id, taskHolder);
     91                 // 添加到 processingOrder 队列的头部,reprocessQueue 是失败重试的队列,所以优先级高一些
     92                 processingOrder.addFirst(id);
     93             }
     94         }
     95         if (isFull()) {
     96             queueOverflows += reprocessQueue.size();
     97             // pendingTasks 满了,就清空 reprocessQueue
     98             reprocessQueue.clear();
     99         }
    100     }
    101 
    102     private void appendTaskHolder(TaskHolder<ID, T> taskHolder) {
    103         if (isFull()) {
    104             // pendingTasks 满了就移除一个元素
    105             pendingTasks.remove(processingOrder.poll());
    106             queueOverflows++;
    107         }
    108         // 将 acceptorQueue 里的任务放到 pendingTasks
    109         TaskHolder<ID, T> previousTask = pendingTasks.put(taskHolder.getId(), taskHolder);
    110         if (previousTask == null) {
    111             // 原本不存在,将任务ID添加到 processingOrder 队列的最后
    112             processingOrder.add(taskHolder.getId());
    113         } else {
    114             // 已经存在了,就是覆盖
    115             overriddenTasks++;
    116         }
    117     }
    118 
    119     void assignSingleItemWork() {
    120         if (!processingOrder.isEmpty()) {
    121             if (singleItemWorkRequests.tryAcquire(1)) {
    122                 long now = System.currentTimeMillis();
    123                 while (!processingOrder.isEmpty()) {
    124                     ID id = processingOrder.poll();
    125                     TaskHolder<ID, T> holder = pendingTasks.remove(id);
    126                     if (holder.getExpiryTime() > now) {
    127                         // 将 pendingTasks 的任务移到 singleItemWorkQueue
    128                         singleItemWorkQueue.add(holder);
    129                         return;
    130                     }
    131                     expiredTasks++;
    132                 }
    133                 singleItemWorkRequests.release();
    134             }
    135         }
    136     }
    137 
    138     void assignBatchWork() {
    139         // 有足够的任务做一个批处理
    140         if (hasEnoughTasksForNextBatch()) {
    141             if (batchWorkRequests.tryAcquire(1)) {
    142                 long now = System.currentTimeMillis();
    143                 // 一批任务最多 250 个
    144                 int len = Math.min(maxBatchingSize, processingOrder.size());
    145                 List<TaskHolder<ID, T>> holders = new ArrayList<>(len);
    146                 // 将 pendingTasks 中的任务移动一批到 holders 中
    147                 // 也就是说,如果队列中有500个任务,这一批任务最多也是250个
    148                 while (holders.size() < len && !processingOrder.isEmpty()) {
    149                     ID id = processingOrder.poll();
    150                     TaskHolder<ID, T> holder = pendingTasks.remove(id);
    151                     if (holder.getExpiryTime() > now) {
    152                         holders.add(holder);
    153                     } else {
    154                         expiredTasks++;
    155                     }
    156                 }
    157                 if (holders.isEmpty()) {
    158                     batchWorkRequests.release();
    159                 } else {
    160                     batchSizeMetric.record(holders.size(), TimeUnit.MILLISECONDS);
    161                     // 添加到批量队列中
    162                     batchWorkQueue.add(holders);
    163                 }
    164             }
    165         }
    166     }
    167 
    168     // 是否有足够的任务做一个批处理
    169     private boolean hasEnoughTasksForNextBatch() {
    170         if (processingOrder.isEmpty()) {
    171             return false;
    172         }
    173         if (pendingTasks.size() >= maxBufferSize) {
    174             return true;
    175         }
    176 
    177         // 从 processingOrder 队首取一个任务ID,然后从 pendingTasks 读取这个任务。注意 peek() 只是取出元素,并不会移除队首的元素
    178         TaskHolder<ID, T> nextHolder = pendingTasks.get(processingOrder.peek());
    179         // 判断任务提交到现在的时间差是否超过最大批任务延迟时间(500毫秒)
    180         long delay = System.currentTimeMillis() - nextHolder.getSubmitTimestamp();
    181         return delay >= maxBatchingDelay;
    182     }
    183 }
    View Code

    先看它的 run 方法:

    ① 队列中的任务转移到待处理容器中

    drainInputQueues 将输入队列(reprocessQueue、acceptorQueue)的任务转移到 pendingTasks 这个待处理容器中。

    先是 drainReprocessQueue 将重处理队列 reprocessQueue 中的任务转移到 pendingTasks:

    • 如果 pendingTasks 已满(超过10000),就直接清空了 reprocessQueue。任务丢弃会不会有影响呢?
    • 否则,如果 reprocessQueue 非空,就从 reprocessQueue 队尾一个个取出来:
      • 如果过期了就丢掉这个任务,说明已经超过续约周期了(90秒)。比如实例注册,如果多次同步失败后,然后就直接丢弃,那不是其它 server 永远无法知道注册的这个实例?后面再分析这个问题。
      • 如果 pendingTasks 已经存在了,也丢弃这个重试任务
      • 否则就添加到 pendingTasks 中,并且往 processingOrder 的头部添加了任务ID
      • 注意它这里是从 reprocessQueue 队尾一个个取出,放入 processingOrder 头部,最终任务在 processingOrder 中的顺序跟 reprocessQueue 是一样的

    然后是 drainAcceptorQueue 将接收者队列 acceptorQueue 中的任务转移到 pendingTasks:

    • 只要 acceptorQueue 非空,就从队首取出任务
    • 如果 pendingTasks 已满,则从 processingOrder 队首取出第一个任务的ID,并从 pendingTasks 中移除这个任务
    • 否则就将任务添加到 pendingTasks,如果之前不存在相同ID的任务,就将任务ID添加到 processingOrder 队尾
    • 注意它这里是从 acceptorQueue 队首取出任务,放到 processingOrder 队尾,最终任务在 processingOrder 中的顺序跟 acceptorQueue 是一样的

    从这段任务转移以及后面的使用来看,processingOrder 将决定任务的处理顺序,最前面的将最先处理,也说明了 reprocessQueue 的优先级比 acceptorQueue 更高。而 pendingTasks 是一个 key-value 的队列,便于快速通过ID读取任务。

     1 private void drainAcceptorQueue() {
     2     while (!acceptorQueue.isEmpty()) {
     3         // 将 acceptorQueue 的任务转移到 pendingTasks
     4         appendTaskHolder(acceptorQueue.poll());
     5     }
     6 }
     7 
     8 private void drainReprocessQueue() {
     9     long now = System.currentTimeMillis();
    10     while (!reprocessQueue.isEmpty() && !isFull()) {
    11         // 从 reprocessQueue 队尾取出任务
    12         TaskHolder<ID, T> taskHolder = reprocessQueue.pollLast();
    13         ID id = taskHolder.getId();
    14         if (taskHolder.getExpiryTime() <= now) {
    15             // 任务过期
    16             expiredTasks++;
    17         } else if (pendingTasks.containsKey(id)) {
    18             // pendingTasks 已存在
    19             overriddenTasks++;
    20         } else {
    21             // 将 reprocessQueue 队列的任务放到 pendingTasks
    22             pendingTasks.put(id, taskHolder);
    23             // 添加到 processingOrder 队列的头部,reprocessQueue 是失败重试的队列,所以优先级高一些
    24             processingOrder.addFirst(id);
    25         }
    26     }
    27     if (isFull()) {
    28         queueOverflows += reprocessQueue.size();
    29         // pendingTasks 满了,就清空 reprocessQueue
    30         reprocessQueue.clear();
    31     }
    32 }
    33 
    34 private void appendTaskHolder(TaskHolder<ID, T> taskHolder) {
    35     if (isFull()) {
    36         // pendingTasks 满了就移除一个元素
    37         pendingTasks.remove(processingOrder.poll());
    38         queueOverflows++;
    39     }
    40     // 将 acceptorQueue 里的任务放到 pendingTasks
    41     TaskHolder<ID, T> previousTask = pendingTasks.put(taskHolder.getId(), taskHolder);
    42     if (previousTask == null) {
    43         // 原本不存在,将任务ID添加到 processingOrder 队列的最后
    44         processingOrder.add(taskHolder.getId());
    45     } else {
    46         // 已经存在了,就是覆盖
    47         overriddenTasks++;
    48     }
    49 }

    ② 接下来通过 trafficShaper 获取了一个补偿时间,它主要是在发生阻塞或网络异常导致任务提交失败后,在任务调度周期内做一个时间补偿,这块等分析到提交任务失败的时候再回来看看。

    1 long now = System.currentTimeMillis();
    2 if (scheduleTime < now) {
    3     // 时间补偿,正常情况下 transmissionDelay() 返回 0
    4     scheduleTime = now + trafficShaper.transmissionDelay();
    5 }

    ③ 任务打包

    接着看 assignBatchWork ,它就是将任务打包成一个批次:

    • 首先调用 hasEnoughTasksForNextBatch 判断是否有足够的任务来打成一个批次,注意它判断了最新提交的任务的时间是否超过了延迟时间 maxBatchingDelay(500ms),也就是说批次任务每隔500毫秒运行一次。
    • 能够打包后,要获取 batchWorkRequests 信号量的一个许可证,因为许可证默认数量是 0,那一定是先有地方调用了 batchWorkRequests.release() 放入许可证,否则这里就不会打包了。
    • 然后可以看出,一个批次的任务数量最多是250个
    • 它从 processingOrder 的队首取出这个批次的任务ID,并从 pendingTasks 中取出任务,如果是过期的任务就直接丢弃了。
    • 然后如果这个批次并没有任务,他才调用 batchWorkRequests.release() 释放了许可证,否则就把这个批次任务添加到批量工作队列 batchWorkQueue 中,注意并没有释放许可证。
     1 void assignBatchWork() {
     2     // 有足够的任务做一个批处理
     3     if (hasEnoughTasksForNextBatch()) {
     4         // 获取许可证
     5         if (batchWorkRequests.tryAcquire(1)) {
     6             long now = System.currentTimeMillis();
     7             // 一批任务最多 250 个
     8             int len = Math.min(maxBatchingSize, processingOrder.size());
     9             List<TaskHolder<ID, T>> holders = new ArrayList<>(len);
    10             // 将 pendingTasks 中的任务移动一批到 holders 中
    11             // 也就是说,如果队列中有500个任务,这一批任务最多也是250个
    12             while (holders.size() < len && !processingOrder.isEmpty()) {
    13                 ID id = processingOrder.poll();
    14                 TaskHolder<ID, T> holder = pendingTasks.remove(id);
    15                 if (holder.getExpiryTime() > now) {
    16                     holders.add(holder);
    17                 } else {
    18                     expiredTasks++;
    19                 }
    20             }
    21             if (holders.isEmpty()) {
    22                 batchWorkRequests.release();
    23             } else {
    24                 batchSizeMetric.record(holders.size(), TimeUnit.MILLISECONDS);
    25                 // 添加到批量队列中
    26                 batchWorkQueue.add(holders);
    27             }
    28         }
    29     }
    30 }
    31 
    32 // 是否有足够的任务做一个批处理
    33 private boolean hasEnoughTasksForNextBatch() {
    34     if (processingOrder.isEmpty()) {
    35         return false;
    36     }
    37     if (pendingTasks.size() >= maxBufferSize) {
    38         return true;
    39     }
    40 
    41     // 从 processingOrder 队首取一个任务ID,然后从 pendingTasks 读取这个任务。注意 peek() 只是取出元素,并不会移除队首的元素
    42     TaskHolder<ID, T> nextHolder = pendingTasks.get(processingOrder.peek());
    43     // 判断任务提交到现在的时间差是否超过最大批任务延迟时间(500毫秒)
    44     long delay = System.currentTimeMillis() - nextHolder.getSubmitTimestamp();
    45     return delay >= maxBatchingDelay;
    46 }

    接着看分配单项任务的方法 assignSingleItemWork:

    • 如果 processingOrder 非空且获取到了 singleItemWorkRequests 信号量的许可证,就将 processingOrder 队列剩余的任务都取出来,放入单项工作队列 singleItemWorkQueue 中
    • 也就是前面已经打了一批任务(250个)之后,processingOrder 中还有任务,就全部取出来放到 singleItemWorkQueue 队列中
     1 void assignSingleItemWork() {
     2     if (!processingOrder.isEmpty()) {
     3         if (singleItemWorkRequests.tryAcquire(1)) {
     4             long now = System.currentTimeMillis();
     5             while (!processingOrder.isEmpty()) {
     6                 ID id = processingOrder.poll();
     7                 TaskHolder<ID, T> holder = pendingTasks.remove(id);
     8                 if (holder.getExpiryTime() > now) {
     9                     // 将 pendingTasks 的任务移到 singleItemWorkQueue
    10                     singleItemWorkQueue.add(holder);
    11                     return;
    12                 }
    13                 expiredTasks++;
    14             }
    15             singleItemWorkRequests.release();
    16         }
    17     }
    18 }

    5、任务处理器 TaskExecutors

    batchWorkQueue 中的批量任务以及 singleItemWorkQueue 中的单项任务都已经准备好了,那是在哪里发送到集群节点的呢,那就是任务执行器 TaskExecutors 了。

    ① 创建 TaskExecutors

    从创建 TaskExecutors 的方法中可以看出:

    • 批量处理任务的类是 BatchWorkerRunnable,它主要就是处理批量任务队列 batchWorkQueue 中的任务
    • 处理单项任务的类是 SingleTaskWorkerRunnable,它主要就是处理单项任务队列 singleItemWorkQueue 中的任务
    • TaskExecutors 创建了一个线程池,batchExecutors 默认有20个工作线程(不太理解他为什么不用JDK现成的线程池。。),singleItemExecutors 默认只有一个工作线程。
     1 static <ID, T> TaskExecutors<ID, T> singleItemExecutors(final String name, int workerCount, final TaskProcessor<T> processor, final AcceptorExecutor<ID, T> acceptorExecutor) {
     2     final AtomicBoolean isShutdown = new AtomicBoolean();
     3     final TaskExecutorMetrics metrics = new TaskExecutorMetrics(name);
     4     registeredMonitors.put(name, metrics);
     5     // workerCount = 1
     6     return new TaskExecutors<>(idx -> new SingleTaskWorkerRunnable<>("TaskNonBatchingWorker-" + name + '-' + idx, isShutdown, metrics, processor, acceptorExecutor), workerCount, isShutdown);
     7 }
     8 
     9 ////////////////////////////////////////////////
    10 
    11 static <ID, T> TaskExecutors<ID, T> batchExecutors(final String name, int workerCount, final TaskProcessor<T> processor, final AcceptorExecutor<ID, T> acceptorExecutor) {
    12     final AtomicBoolean isShutdown = new AtomicBoolean();
    13     final TaskExecutorMetrics metrics = new TaskExecutorMetrics(name);
    14     registeredMonitors.put(name, metrics);
    15     // BatchWorkerRunnable 批量任务处理
    16     return new TaskExecutors<>(idx -> new BatchWorkerRunnable<>("TaskBatchingWorker-" + name + '-' + idx, isShutdown, metrics, processor, acceptorExecutor), workerCount, isShutdown);
    17 }
    18 
    19 ////////////////////////////////////////////////
    20 
    21 private final List<Thread> workerThreads;
    22 
    23 TaskExecutors(WorkerRunnableFactory<ID, T> workerRunnableFactory, int workerCount, AtomicBoolean isShutdown) {
    24     this.isShutdown = isShutdown;
    25     // 工作线程集合
    26     this.workerThreads = new ArrayList<>();
    27 
    28     // 创建20个线程,相当于是搞了一个线程池
    29     ThreadGroup threadGroup = new ThreadGroup("eurekaTaskExecutors");
    30     for (int i = 0; i < workerCount; i++) {
    31         WorkerRunnable<ID, T> runnable = workerRunnableFactory.create(i);
    32         Thread workerThread = new Thread(threadGroup, runnable, runnable.getWorkerName());
    33         workerThreads.add(workerThread);
    34         workerThread.setDaemon(true);
    35         workerThread.start();
    36     }
    37 }

    ② BatchWorkerRunnable

    看批量处理的任务:

    • 首先 getWork 获取批量任务,它调用 taskDispatcher.requestWorkItems(),实际就是返回了 taskDispatcher 的 batchWorkQueue,并且调用 batchWorkRequests.release() 往信号量放入一个许可证,这样前面 AcceptorRunner 就可以得到许可证然后去打包批量任务了
    • 如果 batchWorkQueue 中没有批量任务,可以看到是一直在 while 循环等待的,直到拿到一个批量任务。它这个 BatchWorkerRunnable 任务和前面的 AcceptorRunner 任务,感觉通过信号量的方式就形成了一个等待通知的机制,BatchWorkerRunnable 放入一个许可证,让 AcceptorRunner 拿到这个许可证去打个批次的任务过来。
    • 拿到这个批次任务后,就调用 processor(ReplicationTaskProcessor)来处理任务。
    • 如果任务处理结果是 Congestion(阻塞)、TransientError(传输失败)就要重处理,调用了 taskDispatcher.reprocess 将这个批次的任务提交到重处理队列 reprocessQueue 中。
     1 static class BatchWorkerRunnable<ID, T> extends WorkerRunnable<ID, T> {
     2 
     3     BatchWorkerRunnable(String workerName, AtomicBoolean isShutdown, TaskExecutorMetrics metrics, TaskProcessor<T> processor, AcceptorExecutor<ID, T> acceptorExecutor) {
     4         super(workerName, isShutdown, metrics, processor, acceptorExecutor);
     5     }
     6 
     7     @Override
     8     public void run() {
     9         try {
    10             while (!isShutdown.get()) {
    11                 // 获取一个批量任务
    12                 List<TaskHolder<ID, T>> holders = getWork();
    13                 metrics.registerExpiryTimes(holders);
    14                 // TaskHolder 提取 ReplicationTask
    15                 List<T> tasks = getTasksOf(holders);
    16                 // processor => 任务复制处理器 ReplicationTaskProcessor
    17                 ProcessingResult result = processor.process(tasks);
    18                 switch (result) {
    19                     case Success:
    20                         break;
    21                     case Congestion:
    22                     case TransientError:
    23                         // 阻塞或网络失败就重新处理这批任务
    24                         taskDispatcher.reprocess(holders, result);
    25                         break;
    26                     case PermanentError:
    27                         logger.warn("Discarding {} tasks of {} due to permanent error", holders.size(), workerName);
    28                 }
    29                 metrics.registerTaskResult(result, tasks.size());
    30             }
    31         } catch (InterruptedException e) {
    32             // Ignore
    33         } catch (Throwable e) {
    34             // Safe-guard, so we never exit this loop in an uncontrolled way.
    35             logger.warn("Discovery WorkerThread error", e);
    36         }
    37     }
    38 
    39     private List<TaskHolder<ID, T>> getWork() throws InterruptedException {
    40         // 获取批量队列 batchWorkQueue
    41         BlockingQueue<List<TaskHolder<ID, T>>> workQueue = taskDispatcher.requestWorkItems();
    42         List<TaskHolder<ID, T>> result;
    43         do {
    44             result = workQueue.poll(1, TimeUnit.SECONDS);
    45             // 循环等待,直到取到一个批量任务
    46         } while (!isShutdown.get() && result == null);
    47         return (result == null) ? new ArrayList<>() : result;
    48     }
    49 
    50     private List<T> getTasksOf(List<TaskHolder<ID, T>> holders) {
    51         List<T> tasks = new ArrayList<>(holders.size());
    52         for (TaskHolder<ID, T> holder : holders) {
    53             tasks.add(holder.getTask());
    54         }
    55         return tasks;
    56     }
    57 }
    1 BlockingQueue<TaskHolder<ID, T>> requestWorkItem() {
    2     singleItemWorkRequests.release();
    3     return singleItemWorkQueue;
    4 }
    5 
    6 BlockingQueue<List<TaskHolder<ID, T>>> requestWorkItems() {
    7     batchWorkRequests.release();
    8     return batchWorkQueue;
    9 }

    ③ 任务重处理

    可以看到处理失败后,就是将这批任务添加到重处理队列 reprocessQueue 中去,然后向时间调整期注册失败,这就和前面 AcceptorRunner 处理 reprocessQueue 对应起来了。

    1 void reprocess(List<TaskHolder<ID, T>> holders, ProcessingResult processingResult) {
    2     // 添加到重处理队列 reprocessQueue
    3     reprocessQueue.addAll(holders);
    4     replayedTasks += holders.size();
    5     // 时间调整器注册失败
    6     trafficShaper.registerFailure(processingResult);
    7 }

    ④ TrafficShaper 

    还记得前面 AcceptorRunner 中又这样一段代码,可以看到是通过 trafficShaper 计算了一个延迟时间,这里就来看看是如何计算的。

     1 long now = System.currentTimeMillis();
     2 if (scheduleTime < now) {
     3     // 时间补偿,正常情况下 transmissionDelay() 返回 0
     4     scheduleTime = now + trafficShaper.transmissionDelay();
     5 }
     6 if (scheduleTime <= now) {
     7     // 分配批量工作任务:将 pendingTasks 的任务分一批到(最多250个) batchWorkQueue 队列中
     8     assignBatchWork();
     9     // 分配单项工作任务:pendingTasks 如果还有剩余任务,将没有过期的转移到 singleItemWorkQueue 队列中
    10     assignSingleItemWork();
    11 }

    时间调整器 TrafficShaper:

    • registerFailure 就是设置了失败的最后时间
    • 然后看 transmissionDelay,以阻塞为例,如果上一次阻塞失败到现在 500 毫秒,那么 transmissionDelay 返回 500,那么 transmissionDelay 就大于 now 了,就不会打包任务了。
    • 总结下来就是如果上一次阻塞导致批量任务提交失败,就延迟1000毫秒后执行。如果上一次网络导致批量任务提交失败,就延迟100毫秒执行。
     1 TrafficShaper(long congestionRetryDelayMs, long networkFailureRetryMs) {
     2     // 1000
     3     this.congestionRetryDelayMs = Math.min(MAX_DELAY, congestionRetryDelayMs);
     4     // 100
     5     this.networkFailureRetryMs = Math.min(MAX_DELAY, networkFailureRetryMs);
     6 }
     7 
     8 void registerFailure(ProcessingResult processingResult) {
     9     if (processingResult == ProcessingResult.Congestion) {
    10         // 最后一次阻塞导致提交批处理失败的时间
    11         lastCongestionError = System.currentTimeMillis();
    12     } else if (processingResult == ProcessingResult.TransientError) {
    13         // 最后一次网络原因导致提交批处理失败的时间
    14         lastNetworkFailure = System.currentTimeMillis();
    15     }
    16 }
    17 
    18 // 计算传输延迟的时间
    19 long transmissionDelay() {
    20     if (lastCongestionError == -1 && lastNetworkFailure == -1) {
    21         return 0;
    22     }
    23 
    24     long now = System.currentTimeMillis();
    25     if (lastCongestionError != -1) {
    26         // 阻塞延迟时间
    27         long congestionDelay = now - lastCongestionError;
    28         if (congestionDelay >= 0 && congestionDelay < congestionRetryDelayMs) {
    29             return congestionRetryDelayMs - congestionDelay;
    30         }
    31         lastCongestionError = -1;
    32     }
    33 
    34     if (lastNetworkFailure != -1) {
    35         // 网络延迟时间
    36         long failureDelay = now - lastNetworkFailure;
    37         if (failureDelay >= 0 && failureDelay < networkFailureRetryMs) {
    38             return networkFailureRetryMs - failureDelay;
    39         }
    40         lastNetworkFailure = -1;
    41     }
    42     return 0;
    43 }

    ⑤ SingleTaskWorkerRunnable

    单项任务处理跟批量任务处理的流程是类似的,只不过是一个个的发送同步操作,处理失败同样也会放入重处理队列中。

    一个批量任务250个对于大部分场景来说其实不会触发单项任务的处理,如果微服务集群中有很多的实例,eureka 通过不断的轮询也能尽量使用批量处理,我觉得单项任务处理更像是对批量任务处理的一种补充。

    6、复制任务处理器 ReplicationTaskProcessor

    批量任务最终是提交到 ReplicationTaskProcessor 去处理的,可以看到,就是调用了 replicationClient 提交了批量任务,提交的接口是 POST peerreplication/batch,那我们就可以从这个入口去看 eureka-server 如何接收批量任务的。

     1 public ProcessingResult process(List<ReplicationTask> tasks) {
     2     // 任务封装到 ReplicationList
     3     ReplicationList list = createReplicationListOf(tasks);
     4     try {
     5         // 提交批量任务:POST peerreplication/batch/
     6         EurekaHttpResponse<ReplicationListResponse> response = replicationClient.submitBatchUpdates(list);
     7         int statusCode = response.getStatusCode();
     8         if (!isSuccess(statusCode)) {
     9             if (statusCode == 503) {
    10                 logger.warn("Server busy (503) HTTP status code received from the peer {}; rescheduling tasks after delay", peerId);
    11                 return ProcessingResult.Congestion;
    12             } else {
    13                 // Unexpected error returned from the server. This should ideally never happen.
    14                 logger.error("Batch update failure with HTTP status code {}; discarding {} replication tasks", statusCode, tasks.size());
    15                 return ProcessingResult.PermanentError;
    16             }
    17         } else {
    18             // 处理批量任务结果
    19             handleBatchResponse(tasks, response.getEntity().getResponseList());
    20         }
    21     } catch (Throwable e) {
    22         if (maybeReadTimeOut(e)) {
    23             //read timeout exception is more Congestion then TransientError, return Congestion for longer delay
    24             return ProcessingResult.Congestion;
    25         } else if (isNetworkConnectException(e)) {
    26             logNetworkErrorSample(null, e);
    27             return ProcessingResult.TransientError;
    28         } else {
    29             logger.error("Not re-trying this exception because it does not seem to be a network exception", e);
    30             return ProcessingResult.PermanentError;
    31         }
    32     }
    33     return ProcessingResult.Success;
    34 }

    7、接收复制同步请求

    很容易找到批量任务提交的接口在 PeerReplicationResource 的 batchReplication 方法中。

    可以看到,其实遍历批量任务,然后根据不同的操作类型,调用 XxxResource 接口进行对应的操作。比如注册,就是调用 applicationResource.addInstance 完成实例的注册。

      1 @Path("/{version}/peerreplication")
      2 @Produces({"application/xml", "application/json"})
      3 public class PeerReplicationResource {
      4 
      5     private static final Logger logger = LoggerFactory.getLogger(PeerReplicationResource.class);
      6 
      7     private static final String REPLICATION = "true";
      8 
      9     private final EurekaServerConfig serverConfig;
     10     private final PeerAwareInstanceRegistry registry;
     11 
     12     @Inject
     13     PeerReplicationResource(EurekaServerContext server) {
     14         this.serverConfig = server.getServerConfig();
     15         this.registry = server.getRegistry();
     16     }
     17 
     18     public PeerReplicationResource() {
     19         this(EurekaServerContextHolder.getInstance().getServerContext());
     20     }
     21 
     22     @Path("batch")
     23     @POST
     24     public Response batchReplication(ReplicationList replicationList) {
     25         try {
     26             ReplicationListResponse batchResponse = new ReplicationListResponse();
     27             for (ReplicationInstance instanceInfo : replicationList.getReplicationList()) {
     28                 try {
     29                     // dispatch 分发任务
     30                     batchResponse.addResponse(dispatch(instanceInfo));
     31                 } catch (Exception e) {
     32                     batchResponse.addResponse(new ReplicationInstanceResponse(Status.INTERNAL_SERVER_ERROR.getStatusCode(), null));
     33                     logger.error("{} request processing failed for batch item {}/{}",
     34                             instanceInfo.getAction(), instanceInfo.getAppName(), instanceInfo.getId(), e);
     35                 }
     36             }
     37             return Response.ok(batchResponse).build();
     38         } catch (Throwable e) {
     39             logger.error("Cannot execute batch Request", e);
     40             return Response.status(Status.INTERNAL_SERVER_ERROR).build();
     41         }
     42     }
     43 
     44     private ReplicationInstanceResponse dispatch(ReplicationInstance instanceInfo) {
     45         ApplicationResource applicationResource = createApplicationResource(instanceInfo);
     46         InstanceResource resource = createInstanceResource(instanceInfo, applicationResource);
     47 
     48         String lastDirtyTimestamp = toString(instanceInfo.getLastDirtyTimestamp());
     49         String overriddenStatus = toString(instanceInfo.getOverriddenStatus());
     50         String instanceStatus = toString(instanceInfo.getStatus());
     51 
     52         Builder singleResponseBuilder = new Builder();
     53         // 根据不同的类型分别处理
     54         switch (instanceInfo.getAction()) {
     55             case Register:
     56                 singleResponseBuilder = handleRegister(instanceInfo, applicationResource);
     57                 break;
     58             case Heartbeat:
     59                 singleResponseBuilder = handleHeartbeat(serverConfig, resource, lastDirtyTimestamp, overriddenStatus, instanceStatus);
     60                 break;
     61             case Cancel:
     62                 singleResponseBuilder = handleCancel(resource);
     63                 break;
     64             case StatusUpdate:
     65                 singleResponseBuilder = handleStatusUpdate(instanceInfo, resource);
     66                 break;
     67             case DeleteStatusOverride:
     68                 singleResponseBuilder = handleDeleteStatusOverride(instanceInfo, resource);
     69                 break;
     70         }
     71         return singleResponseBuilder.build();
     72     }
     73 
     74     /* Visible for testing */ ApplicationResource createApplicationResource(ReplicationInstance instanceInfo) {
     75         return new ApplicationResource(instanceInfo.getAppName(), serverConfig, registry);
     76     }
     77 
     78     /* Visible for testing */ InstanceResource createInstanceResource(ReplicationInstance instanceInfo,
     79                                                                       ApplicationResource applicationResource) {
     80         return new InstanceResource(applicationResource, instanceInfo.getId(), serverConfig, registry);
     81     }
     82 
     83     private static Builder handleRegister(ReplicationInstance instanceInfo, ApplicationResource applicationResource) {
     84         // addInstance
     85         applicationResource.addInstance(instanceInfo.getInstanceInfo(), REPLICATION);
     86         return new Builder().setStatusCode(Status.OK.getStatusCode());
     87     }
     88 
     89     private static Builder handleCancel(InstanceResource resource) {
     90         // cancelLease
     91         Response response = resource.cancelLease(REPLICATION);
     92         return new Builder().setStatusCode(response.getStatus());
     93     }
     94 
     95     private static Builder handleHeartbeat(EurekaServerConfig config, InstanceResource resource, String lastDirtyTimestamp, String overriddenStatus, String instanceStatus) {
     96         Response response = resource.renewLease(REPLICATION, overriddenStatus, instanceStatus, lastDirtyTimestamp);
     97         int responseStatus = response.getStatus();
     98         Builder responseBuilder = new Builder().setStatusCode(responseStatus);
     99 
    100         if ("false".equals(config.getExperimental("bugfix.934"))) {
    101             if (responseStatus == Status.OK.getStatusCode() && response.getEntity() != null) {
    102                 responseBuilder.setResponseEntity((InstanceInfo) response.getEntity());
    103             }
    104         } else {
    105             if ((responseStatus == Status.OK.getStatusCode() || responseStatus == Status.CONFLICT.getStatusCode())
    106                     && response.getEntity() != null) {
    107                 responseBuilder.setResponseEntity((InstanceInfo) response.getEntity());
    108             }
    109         }
    110         return responseBuilder;
    111     }
    112 
    113     private static Builder handleStatusUpdate(ReplicationInstance instanceInfo, InstanceResource resource) {
    114         Response response = resource.statusUpdate(instanceInfo.getStatus(), REPLICATION, toString(instanceInfo.getLastDirtyTimestamp()));
    115         return new Builder().setStatusCode(response.getStatus());
    116     }
    117 
    118     private static Builder handleDeleteStatusOverride(ReplicationInstance instanceInfo, InstanceResource resource) {
    119         Response response = resource.deleteStatusUpdate(REPLICATION, instanceInfo.getStatus(),
    120                 instanceInfo.getLastDirtyTimestamp().toString());
    121         return new Builder().setStatusCode(response.getStatus());
    122     }
    123 
    124     private static <T> String toString(T value) {
    125         if (value == null) {
    126             return null;
    127         }
    128         return value.toString();
    129     }
    130 }
    View Code

    8、集群数据同步冲突问题

    Peer to Peer 模式重点要解决的一个问题是数据复制冲突的问题,因为 peer 节点间的相互复制并不能保证所有操作都成功。eureka 主要通过 lastDirtyTimestamp 标识和心跳来进行数据的最终修复,下面就来看下 eureka 如何处理数据冲突问题的。

    ① 先看续约的这个方法

    • 在续约 renewLease 里,如果 lastDirtyTimestamp 不为空且允许时间戳不一致时进行同步(默认开启),就调用了 validateDirtyTimestamp 方法校验 lastDirtyTimestamp。
    • 接着看 validateDirtyTimestamp,如果 lastDirtyTimestamp 与本地实例的 lastDirtyTimestamp 一致,说明数据是一致的,就续约成功,返回 OK(200)。
    • 如果 lastDirtyTimestamp 大于 本地实例的 lastDirtyTimestamp,说明复制的实例最新更新的,出现数据冲突,返回 NOT_FOUND(404)。
    • 如果 lastDirtyTimestamp 小于 本地实例的 lastDirtyTimestamp ,说明复制的实例是旧的,出现数据冲突,返回 CONFLICT(409),并且返回了本地的实例。
     1 @PUT
     2 public Response renewLease(
     3         @HeaderParam(PeerEurekaNode.HEADER_REPLICATION) String isReplication,
     4         @QueryParam("overriddenstatus") String overriddenStatus,
     5         @QueryParam("status") String status,
     6         @QueryParam("lastDirtyTimestamp") String lastDirtyTimestamp) {
     7     boolean isFromReplicaNode = "true".equals(isReplication);
     8     // 调用注册表的 renew 进行服务续约
     9     boolean isSuccess = registry.renew(app.getName(), id, isFromReplicaNode);
    10 
    11     // Not found in the registry, immediately ask for a register
    12     if (!isSuccess) {
    13         logger.warn("Not Found (Renew): {} - {}", app.getName(), id);
    14         return Response.status(Status.NOT_FOUND).build();
    15     }
    16     // Check if we need to sync based on dirty time stamp, the client
    17     // instance might have changed some value
    18     Response response;
    19     // 如果是复制操作,就校验 lastDirtyTimestamp
    20     if (lastDirtyTimestamp != null && serverConfig.shouldSyncWhenTimestampDiffers()) {
    21         response = this.validateDirtyTimestamp(Long.valueOf(lastDirtyTimestamp), isFromReplicaNode);
    22         // Store the overridden status since the validation found out the node that replicates wins
    23         if (response.getStatus() == Response.Status.NOT_FOUND.getStatusCode()
    24                 && (overriddenStatus != null)
    25                 && !(InstanceStatus.UNKNOWN.name().equals(overriddenStatus))
    26                 && isFromReplicaNode) {
    27             registry.storeOverriddenStatusIfRequired(app.getAppName(), id, InstanceStatus.valueOf(overriddenStatus));
    28         }
    29     } else {
    30         response = Response.ok().build();
    31     }
    32     logger.debug("Found (Renew): {} - {}; reply status={}", app.getName(), id, response.getStatus());
    33     return response;
    34 }
    35 
    36 ///////////////////////////////////////////
    37 
    38 private Response validateDirtyTimestamp(Long lastDirtyTimestamp, boolean isReplication) {
    39     InstanceInfo appInfo = registry.getInstanceByAppAndId(app.getName(), id, false);
    40     if (appInfo != null) {
    41         // 如果复制传过来的实例中 lastDirtyTimestamp 不等于本地实例的 lastDirtyTimestamp
    42         if ((lastDirtyTimestamp != null) && (!lastDirtyTimestamp.equals(appInfo.getLastDirtyTimestamp()))) {
    43             Object[] args = {id, appInfo.getLastDirtyTimestamp(), lastDirtyTimestamp, isReplication};
    44 
    45             if (lastDirtyTimestamp > appInfo.getLastDirtyTimestamp()) {
    46                 logger.debug(
    47                         "Time to sync, since the last dirty timestamp differs -"
    48                                 + " ReplicationInstance id : {},Registry : {} Incoming: {} Replication: {}",
    49                         args);
    50                 // 如果复制实例的 lastDirtyTimestamp > 本地实例的 lastDirtyTimestamp,表示数据出现冲突,返回 404,要求应用实例重新进行 register 操作
    51                 return Response.status(Status.NOT_FOUND).build();
    52             } else if (appInfo.getLastDirtyTimestamp() > lastDirtyTimestamp) {
    53                 // In the case of replication, send the current instance info in the registry for the
    54                 // replicating node to sync itself with this one.
    55                 if (isReplication) {
    56                     logger.debug(
    57                             "Time to sync, since the last dirty timestamp differs -"
    58                                     + " ReplicationInstance id : {},Registry : {} Incoming: {} Replication: {}",
    59                             args);
    60                     // 如果本地实例的 lastDirtyTimestamp > 复制实例的 lastDirtyTimestamp,就返回 CONFLICT(409),说明数据冲突,要求其同步自己最新的数据
    61                     // 注意这里将本地实例 appInfo 放入 Response entity 中了
    62                     return Response.status(Status.CONFLICT).entity(appInfo).build();
    63                 } else {
    64                     return Response.ok().build();
    65                 }
    66             }
    67         }
    68 
    69     }
    70     return Response.ok().build();
    71 }

    ② 接着看 PeerReplicationResource 处理心跳的方法

    • 首先就是调用了续约的方法 renewLease 进行续约
    • 如果返回的状态是 OK 或者 CONFLICT,就在 resposeEntity 中返回本地实例
     1 private static Builder handleHeartbeat(EurekaServerConfig config, InstanceResource resource, String lastDirtyTimestamp, String overriddenStatus, String instanceStatus) {
     2     // 调用 renewLease 续约
     3     Response response = resource.renewLease(REPLICATION, overriddenStatus, instanceStatus, lastDirtyTimestamp);
     4     int responseStatus = response.getStatus();
     5     Builder responseBuilder = new Builder().setStatusCode(responseStatus);
     6 
     7     if ("false".equals(config.getExperimental("bugfix.934"))) {
     8         if (responseStatus == Status.OK.getStatusCode() && response.getEntity() != null) {
     9             responseBuilder.setResponseEntity((InstanceInfo) response.getEntity());
    10         }
    11     } else {
    12         if ((responseStatus == Status.OK.getStatusCode() || responseStatus == Status.CONFLICT.getStatusCode())
    13                 && response.getEntity() != null) {
    14             // 续约成功或 CONFLICT 冲突时,将本地实例 appInfo 返回到客户端
    15             responseBuilder.setResponseEntity((InstanceInfo) response.getEntity());
    16         }
    17     }
    18     return responseBuilder;
    19 }

    ③ PeerEurekaNode 发送心跳

    ReplicationTaskProcessor 收到批量任务返回结果后,会处理响应结果,对于心跳任务,可以找到,失败后就会回调 handleFailure 方法。

    • 如果返回状态是 404(NOT_FOUND),就会重新注册,也是提交到队列中。通过重新注册来实现数据同步。
    • 如果是其它状态(409 CONFLICT)并且开启了时间戳不一致就同步的配置,就将服务端返回的实例注册到本地,实现数据的同步。
     1 public void heartbeat(final String appName, final String id,
     2                       final InstanceInfo info, final InstanceStatus overriddenStatus,
     3                       boolean primeConnection) throws Throwable {
     4     if (primeConnection) {
     5         // We do not care about the result for priming request.
     6         replicationClient.sendHeartBeat(appName, id, info, overriddenStatus);
     7         return;
     8     }
     9     ReplicationTask replicationTask = new InstanceReplicationTask(targetHost, Action.Heartbeat, info, overriddenStatus, false) {
    10         @Override
    11         public EurekaHttpResponse<InstanceInfo> execute() throws Throwable {
    12             return replicationClient.sendHeartBeat(appName, id, info, overriddenStatus);
    13         }
    14 
    15         @Override
    16         public void handleFailure(int statusCode, Object responseEntity) throws Throwable {
    17             super.handleFailure(statusCode, responseEntity);
    18             if (statusCode == 404) {
    19                 logger.warn("{}: missing entry.", getTaskName());
    20                 if (info != null) {
    21                     logger.warn("{}: cannot find instance id {} and hence replicating the instance with status {}",
    22                             getTaskName(), info.getId(), info.getStatus());
    23                     // 复制返回 404 时,重新注册
    24                     register(info);
    25                 }
    26             } else if (config.shouldSyncWhenTimestampDiffers()) {
    27                 // 409(CONFLICT)
    28                 InstanceInfo peerInstanceInfo = (InstanceInfo) responseEntity;
    29                 if (peerInstanceInfo != null) {
    30                     syncInstancesIfTimestampDiffers(appName, id, info, peerInstanceInfo);
    31                 }
    32             }
    33         }
    34     };
    35     long expiryTime = System.currentTimeMillis() + getLeaseRenewalOf(info);
    36     batchingDispatcher.process(taskId("heartbeat", info), replicationTask, expiryTime);
    37 }
    38 
    39 //////////////////////////////////////////
    40 
    41 private void syncInstancesIfTimestampDiffers(String appName, String id, InstanceInfo info, InstanceInfo infoFromPeer) {
    42     try {
    43         if (infoFromPeer != null) {
    44             if (infoFromPeer.getOverriddenStatus() != null && !InstanceStatus.UNKNOWN.equals(infoFromPeer.getOverriddenStatus())) {
    45                 logger.warn("Overridden Status info -id {}, mine {}, peer's {}", id, info.getOverriddenStatus(), infoFromPeer.getOverriddenStatus());
    46                 registry.storeOverriddenStatusIfRequired(appName, id, infoFromPeer.getOverriddenStatus());
    47             }
    48             // 将服务端的实例注册到本地,实现数据同步
    49             registry.register(infoFromPeer, true);
    50         }
    51     } catch (Throwable e) {
    52         logger.warn("Exception when trying to set information from peer :", e);
    53     }
    54 }

    至此,我们就可以总结出,eureka server 通过对比 lastDirtyTimestamp 和心跳操作来实现集群数据的复制和最终同步。

    前面提到的实例过期就丢弃任务这样看来就没问题,它也不保证peer节点间相互复制的所有操作都成功,eureka 采用的是最终一致性,它是通过心跳的方式实现集群数据的最终修复和同步,只是集群间可能会同步延迟。

    9、集群同步总结

    下面总结下 eureka-server 集群节点间的同步:

    • 首先 eureka-server 集群采用的是 Peer To Peer 的模式,即对等复制,各个 server 不分主从,每个 server 都可以接收写请求,然后互相之间进行数据更新同步。
    • 数据同步采用了多层任务队列+批量处理的机制:
      • eureka-server 接收到客户端请求(注册、下线、续约)后都会调用集群 PeerEurekaNode 进行操作的同步
      • PeerEurekaNode  将操作封装成 InstanceReplicationTask 实例复制任务,并用批量分发器 batchingDispatcher(TaskDispatcher)来分发处理
      • batchingDispatcher 内部则将任务交给接收者执行器 AcceptorExecutor 处理,任务首先进入到 AcceptorExecutor 内的接收者队列 acceptorQueue 中
      • AcceptorExecutor 有个后台工作线程(AcceptorRunner)不断轮询,将接收者队列 acceptorQueue 和 重处理队列 reprocessQueue 中的任务转移到处理中队列中(processingOrder + pendingTasks)
      • 接着将处理中队列中的任务打包,一次最多 250 个任务,然后放到批量工作队列 batchWorkQueue。如果处理中队列中还有任务,就将任务放到单项任务队列 singleItemWorkQueue
      • 任务都打包好了,任务执行器 TaskExecutors 内分别有批量任务处理器(BatchWorkerRunnable)和单项任务处理器(SingleTaskWorkerRunnable)来处理 batchWorkQueue 和 singleItemWorkQueue 中的任务
      • 处理器会利用任务复制处理器(ReplicationTaskProcessor)来提交任务,批量任务会提交给 server 节点的批量接口(peerreplication/batch/),单项任务则提交到对应的操作接口
      • 任务提交如果阻塞或者网络失败就会被放入重处理队列 reprocessQueue,然后再次被 AcceptorRunner 轮询处理,不过过期(超过90秒)的任务会被丢弃掉
    • 其它 eureka-server 同步:
      • 其它 eureka-server 接收到批量复制请求后,会轮询批量任务列表,根据不同的操作类型(Register、Heartbeat、Cancel 等)分别调用 Resource 的接口进行处理
      • 如果是续约操作,会判断复制实例的 lastDirtyTimestamp 与本地实例的 lastDirtyTimestamp,如果是一致的,就任务数据一致
      • 如果复制实例的 lastDirtyTimestamp > 本地实例的 lastDirtyTimestamp,则复制实例的数据是最新的,返回 404(NOT_FOUND) 要求客户端重新发送一个注册操作过来
      • 如果复制实例的 lastDirtyTimestamp < 本地实例的 lastDirtyTimestamp,则本地实例的数据是最新的,返回 409(CONFLICT)和本地实例,客户端用返回来的实例覆盖本地的实例

    下面再用一张图总结集群同步:

    十三、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 技术栈中,便于使用。

    1、spring-cloud-starter-netflix-eureka-server

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

    ① Eureka Server 自动化配置

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

     1 /**
     2  * Annotation to activate Eureka Server related configuration.
     3  * {@link EurekaServerAutoConfiguration}
     4  *
     5  * @author Dave Syer
     6  * @author Biju Kunjummen
     7  */
     8 @Target(ElementType.TYPE)
     9 @Retention(RetentionPolicy.RUNTIME)
    10 @Documented
    11 @Import(EurekaServerMarkerConfiguration.class)
    12 public @interface EnableEurekaServer {
    13 
    14 }

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

      1 @Configuration(proxyBeanMethods = false)
      2 @Import(EurekaServerInitializerConfiguration.class)
      3 @ConditionalOnBean(EurekaServerMarkerConfiguration.Marker.class)
      4 @EnableConfigurationProperties({ EurekaDashboardProperties.class,
      5         InstanceRegistryProperties.class })
      6 @PropertySource("classpath:/eureka/server.properties")
      7 public class EurekaServerAutoConfiguration implements WebMvcConfigurer {
      8 
      9     /**
     10      * List of packages containing Jersey resources required by the Eureka server.
     11      */
     12     private static final String[] EUREKA_PACKAGES = new String[] {
     13             "com.netflix.discovery", "com.netflix.eureka" };
     14 
     15     @Autowired
     16     private ApplicationInfoManager applicationInfoManager;
     17 
     18     @Autowired
     19     private EurekaServerConfig eurekaServerConfig;
     20 
     21     @Autowired
     22     private EurekaClientConfig eurekaClientConfig;
     23 
     24     @Autowired
     25     private EurekaClient eurekaClient;
     26 
     27     @Autowired
     28     private InstanceRegistryProperties instanceRegistryProperties;
     29 
     30     /**
     31      * A {@link CloudJacksonJson} instance.
     32      */
     33     public static final CloudJacksonJson JACKSON_JSON = new CloudJacksonJson();
     34 
     35     @Bean
     36     public HasFeatures eurekaServerFeature() {
     37         return HasFeatures.namedFeature("Eureka Server",
     38                 EurekaServerAutoConfiguration.class);
     39     }
     40 
     41     @Bean
     42     @ConditionalOnProperty(prefix = "eureka.dashboard", name = "enabled",
     43             matchIfMissing = true)
     44     public EurekaController eurekaController() {
     45         return new EurekaController(this.applicationInfoManager);
     46     }
     47 
     48     static {
     49         CodecWrappers.registerWrapper(JACKSON_JSON);
     50         EurekaJacksonCodec.setInstance(JACKSON_JSON.getCodec());
     51     }
     52 
     53     @Bean
     54     public ServerCodecs serverCodecs() {
     55         return new CloudServerCodecs(this.eurekaServerConfig);
     56     }
     57 
     58     private static CodecWrapper getFullJson(EurekaServerConfig serverConfig) {
     59         CodecWrapper codec = CodecWrappers.getCodec(serverConfig.getJsonCodecName());
     60         return codec == null ? CodecWrappers.getCodec(JACKSON_JSON.codecName()) : codec;
     61     }
     62 
     63     private static CodecWrapper getFullXml(EurekaServerConfig serverConfig) {
     64         CodecWrapper codec = CodecWrappers.getCodec(serverConfig.getXmlCodecName());
     65         return codec == null ? CodecWrappers.getCodec(CodecWrappers.XStreamXml.class)
     66                 : codec;
     67     }
     68 
     69     @Bean
     70     @ConditionalOnMissingBean
     71     public ReplicationClientAdditionalFilters replicationClientAdditionalFilters() {
     72         return new ReplicationClientAdditionalFilters(Collections.emptySet());
     73     }
     74 
     75     @Bean
     76     public PeerAwareInstanceRegistry peerAwareInstanceRegistry(
     77             ServerCodecs serverCodecs) {
     78         this.eurekaClient.getApplications(); // force initialization
     79         return new InstanceRegistry(this.eurekaServerConfig, this.eurekaClientConfig,
     80                 serverCodecs, this.eurekaClient,
     81                 this.instanceRegistryProperties.getExpectedNumberOfClientsSendingRenews(),
     82                 this.instanceRegistryProperties.getDefaultOpenForTrafficCount());
     83     }
     84 
     85     @Bean
     86     @ConditionalOnMissingBean
     87     public PeerEurekaNodes peerEurekaNodes(PeerAwareInstanceRegistry registry,
     88             ServerCodecs serverCodecs,
     89             ReplicationClientAdditionalFilters replicationClientAdditionalFilters) {
     90         return new RefreshablePeerEurekaNodes(registry, this.eurekaServerConfig,
     91                 this.eurekaClientConfig, serverCodecs, this.applicationInfoManager,
     92                 replicationClientAdditionalFilters);
     93     }
     94 
     95     @Bean
     96     @ConditionalOnMissingBean
     97     public EurekaServerContext eurekaServerContext(ServerCodecs serverCodecs,
     98             PeerAwareInstanceRegistry registry, PeerEurekaNodes peerEurekaNodes) {
     99         return new DefaultEurekaServerContext(this.eurekaServerConfig, serverCodecs,
    100                 registry, peerEurekaNodes, this.applicationInfoManager);
    101     }
    102 
    103     @Bean
    104     public EurekaServerBootstrap eurekaServerBootstrap(PeerAwareInstanceRegistry registry,
    105             EurekaServerContext serverContext) {
    106         return new EurekaServerBootstrap(this.applicationInfoManager,
    107                 this.eurekaClientConfig, this.eurekaServerConfig, registry,
    108                 serverContext);
    109     }
    110 
    111     /**
    112      * Register the Jersey filter.
    113      * @param eurekaJerseyApp an {@link Application} for the filter to be registered
    114      * @return a jersey {@link FilterRegistrationBean}
    115      */
    116     @Bean
    117     public FilterRegistrationBean<?> jerseyFilterRegistration(
    118             javax.ws.rs.core.Application eurekaJerseyApp) {
    119         FilterRegistrationBean<Filter> bean = new FilterRegistrationBean<Filter>();
    120         bean.setFilter(new ServletContainer(eurekaJerseyApp));
    121         bean.setOrder(Ordered.LOWEST_PRECEDENCE);
    122         bean.setUrlPatterns(
    123                 Collections.singletonList(EurekaConstants.DEFAULT_PREFIX + "/*"));
    124 
    125         return bean;
    126     }
    127 
    128     /**
    129      * Construct a Jersey {@link javax.ws.rs.core.Application} with all the resources
    130      * required by the Eureka server.
    131      * @param environment an {@link Environment} instance to retrieve classpath resources
    132      * @param resourceLoader a {@link ResourceLoader} instance to get classloader from
    133      * @return created {@link Application} object
    134      */
    135     @Bean
    136     public javax.ws.rs.core.Application jerseyApplication(Environment environment,
    137             ResourceLoader resourceLoader) {
    138 
    139         ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(
    140                 false, environment);
    141 
    142         // Filter to include only classes that have a particular annotation.
    143         //
    144         provider.addIncludeFilter(new AnnotationTypeFilter(Path.class));
    145         provider.addIncludeFilter(new AnnotationTypeFilter(Provider.class));
    146 
    147         // Find classes in Eureka packages (or subpackages)
    148         //
    149         Set<Class<?>> classes = new HashSet<>();
    150         for (String basePackage : EUREKA_PACKAGES) {
    151             Set<BeanDefinition> beans = provider.findCandidateComponents(basePackage);
    152             for (BeanDefinition bd : beans) {
    153                 Class<?> cls = ClassUtils.resolveClassName(bd.getBeanClassName(),
    154                         resourceLoader.getClassLoader());
    155                 classes.add(cls);
    156             }
    157         }
    158 
    159         // Construct the Jersey ResourceConfig
    160         Map<String, Object> propsAndFeatures = new HashMap<>();
    161         propsAndFeatures.put(
    162                 // Skip static content used by the webapp
    163                 ServletContainer.PROPERTY_WEB_PAGE_CONTENT_REGEX,
    164                 EurekaConstants.DEFAULT_PREFIX + "/(fonts|images|css|js)/.*");
    165 
    166         DefaultResourceConfig rc = new DefaultResourceConfig(classes);
    167         rc.setPropertiesAndFeatures(propsAndFeatures);
    168 
    169         return rc;
    170     }
    171 
    172     @Bean
    173     @ConditionalOnBean(name = "httpTraceFilter")
    174     public FilterRegistrationBean<?> traceFilterRegistration(
    175             @Qualifier("httpTraceFilter") Filter filter) {
    176         FilterRegistrationBean<Filter> bean = new FilterRegistrationBean<Filter>();
    177         bean.setFilter(filter);
    178         bean.setOrder(Ordered.LOWEST_PRECEDENCE - 10);
    179         return bean;
    180     }
    181 
    182     @Configuration(proxyBeanMethods = false)
    183     protected static class EurekaServerConfigBeanConfiguration {
    184 
    185         @Bean
    186         @ConditionalOnMissingBean
    187         public EurekaServerConfig eurekaServerConfig(EurekaClientConfig clientConfig) {
    188             EurekaServerConfigBean server = new EurekaServerConfigBean();
    189             if (clientConfig.shouldRegisterWithEureka()) {
    190                 // Set a sensible default if we are supposed to replicate
    191                 server.setRegistrySyncRetries(5);
    192             }
    193             return server;
    194         }
    195 
    196     }
    197 
    198     /**
    199      * {@link PeerEurekaNodes} which updates peers when /refresh is invoked. Peers are
    200      * updated only if <code>eureka.client.use-dns-for-fetching-service-urls</code> is
    201      * <code>false</code> and one of following properties have changed.
    202      * <p>
    203      * </p>
    204      * <ul>
    205      * <li><code>eureka.client.availability-zones</code></li>
    206      * <li><code>eureka.client.region</code></li>
    207      * <li><code>eureka.client.service-url.&lt;zone&gt;</code></li>
    208      * </ul>
    209      */
    210     static class RefreshablePeerEurekaNodes extends PeerEurekaNodes
    211             implements ApplicationListener<EnvironmentChangeEvent> {
    212 
    213         private ReplicationClientAdditionalFilters replicationClientAdditionalFilters;
    214 
    215         RefreshablePeerEurekaNodes(final PeerAwareInstanceRegistry registry,
    216                 final EurekaServerConfig serverConfig,
    217                 final EurekaClientConfig clientConfig, final ServerCodecs serverCodecs,
    218                 final ApplicationInfoManager applicationInfoManager,
    219                 final ReplicationClientAdditionalFilters replicationClientAdditionalFilters) {
    220             super(registry, serverConfig, clientConfig, serverCodecs,
    221                     applicationInfoManager);
    222             this.replicationClientAdditionalFilters = replicationClientAdditionalFilters;
    223         }
    224 
    225         @Override
    226         protected PeerEurekaNode createPeerEurekaNode(String peerEurekaNodeUrl) {
    227             JerseyReplicationClient replicationClient = JerseyReplicationClient
    228                     .createReplicationClient(serverConfig, serverCodecs,
    229                             peerEurekaNodeUrl);
    230 
    231             this.replicationClientAdditionalFilters.getFilters()
    232                     .forEach(replicationClient::addReplicationClientFilter);
    233 
    234             String targetHost = hostFromUrl(peerEurekaNodeUrl);
    235             if (targetHost == null) {
    236                 targetHost = "host";
    237             }
    238             return new PeerEurekaNode(registry, targetHost, peerEurekaNodeUrl,
    239                     replicationClient, serverConfig);
    240         }
    241 
    242         @Override
    243         public void onApplicationEvent(final EnvironmentChangeEvent event) {
    244             if (shouldUpdate(event.getKeys())) {
    245                 updatePeerEurekaNodes(resolvePeerUrls());
    246             }
    247         }
    248 
    249         /*
    250          * Check whether specific properties have changed.
    251          */
    252         protected boolean shouldUpdate(final Set<String> changedKeys) {
    253             assert changedKeys != null;
    254 
    255             // if eureka.client.use-dns-for-fetching-service-urls is true, then
    256             // service-url will not be fetched from environment.
    257             if (this.clientConfig.shouldUseDnsForFetchingServiceUrls()) {
    258                 return false;
    259             }
    260 
    261             if (changedKeys.contains("eureka.client.region")) {
    262                 return true;
    263             }
    264 
    265             for (final String key : changedKeys) {
    266                 // property keys are not expected to be null.
    267                 if (key.startsWith("eureka.client.service-url.")
    268                         || key.startsWith("eureka.client.availability-zones.")) {
    269                     return true;
    270                 }
    271             }
    272             return false;
    273         }
    274 
    275     }
    276 
    277     class CloudServerCodecs extends DefaultServerCodecs {
    278 
    279         CloudServerCodecs(EurekaServerConfig serverConfig) {
    280             super(getFullJson(serverConfig),
    281                     CodecWrappers.getCodec(CodecWrappers.JacksonJsonMini.class),
    282                     getFullXml(serverConfig),
    283                     CodecWrappers.getCodec(CodecWrappers.JacksonXmlMini.class));
    284         }
    285 
    286     }
    287 
    288 }
    View Code

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

    ② Springboot 方式的配置

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

    例如 EurekaServerConfigBean:

       1 @ConfigurationProperties(EurekaServerConfigBean.PREFIX)
       2 public class EurekaServerConfigBean implements EurekaServerConfig {
       3 
       4     /**
       5      * Eureka server configuration properties prefix.
       6      */
       7     public static final String PREFIX = "eureka.server";
       8 
       9     private static final int MINUTES = 60 * 1000;
      10 
      11     @Autowired(required = false)
      12     PropertyResolver propertyResolver;
      13 
      14     private String aWSAccessId;
      15 
      16     private String aWSSecretKey;
      17 
      18     private int eIPBindRebindRetries = 3;
      19 
      20     private int eIPBindingRetryIntervalMs = 5 * MINUTES;
      21 
      22     private int eIPBindingRetryIntervalMsWhenUnbound = 1 * MINUTES;
      23 
      24     private boolean enableSelfPreservation = true;
      25 
      26     private double renewalPercentThreshold = 0.85;
      27 
      28     private int renewalThresholdUpdateIntervalMs = 15 * MINUTES;
      29 
      30     private int peerEurekaNodesUpdateIntervalMs = 10 * MINUTES;
      31 
      32     private int numberOfReplicationRetries = 5;
      33 
      34     private int peerEurekaStatusRefreshTimeIntervalMs = 30 * 1000;
      35 
      36     private int waitTimeInMsWhenSyncEmpty = 5 * MINUTES;
      37 
      38     private int peerNodeConnectTimeoutMs = 200;
      39 
      40     private int peerNodeReadTimeoutMs = 200;
      41 
      42     private int peerNodeTotalConnections = 1000;
      43 
      44     private int peerNodeTotalConnectionsPerHost = 500;
      45 
      46     private int peerNodeConnectionIdleTimeoutSeconds = 30;
      47 
      48     private long retentionTimeInMSInDeltaQueue = 3 * MINUTES;
      49 
      50     private long deltaRetentionTimerIntervalInMs = 30 * 1000;
      51 
      52     private long evictionIntervalTimerInMs = 60 * 1000;
      53 
      54     private int aSGQueryTimeoutMs = 300;
      55 
      56     private long aSGUpdateIntervalMs = 5 * MINUTES;
      57 
      58     private long aSGCacheExpiryTimeoutMs = 10 * MINUTES; // defaults to longer than the
      59 
      60     // asg update interval
      61 
      62     private long responseCacheAutoExpirationInSeconds = 180;
      63 
      64     private long responseCacheUpdateIntervalMs = 30 * 1000;
      65 
      66     private boolean useReadOnlyResponseCache = true;
      67 
      68     private boolean disableDelta;
      69 
      70     private long maxIdleThreadInMinutesAgeForStatusReplication = 10;
      71 
      72     private int minThreadsForStatusReplication = 1;
      73 
      74     private int maxThreadsForStatusReplication = 1;
      75 
      76     private int maxElementsInStatusReplicationPool = 10000;
      77 
      78     private boolean syncWhenTimestampDiffers = true;
      79 
      80     private int registrySyncRetries = 0;
      81 
      82     private long registrySyncRetryWaitMs = 30 * 1000;
      83 
      84     private int maxElementsInPeerReplicationPool = 10000;
      85 
      86     private long maxIdleThreadAgeInMinutesForPeerReplication = 15;
      87 
      88     private int minThreadsForPeerReplication = 5;
      89 
      90     private int maxThreadsForPeerReplication = 20;
      91 
      92     private int maxTimeForReplication = 30000;
      93 
      94     private boolean primeAwsReplicaConnections = true;
      95 
      96     private boolean disableDeltaForRemoteRegions;
      97 
      98     private int remoteRegionConnectTimeoutMs = 1000;
      99 
     100     private int remoteRegionReadTimeoutMs = 1000;
     101 
     102     private int remoteRegionTotalConnections = 1000;
     103 
     104     private int remoteRegionTotalConnectionsPerHost = 500;
     105 
     106     private int remoteRegionConnectionIdleTimeoutSeconds = 30;
     107 
     108     private boolean gZipContentFromRemoteRegion = true;
     109 
     110     private Map<String, String> remoteRegionUrlsWithName = new HashMap<>();
     111 
     112     private String[] remoteRegionUrls;
     113 
     114     private Map<String, Set<String>> remoteRegionAppWhitelist = new HashMap<>();
     115 
     116     private int remoteRegionRegistryFetchInterval = 30;
     117 
     118     private int remoteRegionFetchThreadPoolSize = 20;
     119 
     120     private String remoteRegionTrustStore = "";
     121 
     122     private String remoteRegionTrustStorePassword = "changeit";
     123 
     124     private boolean disableTransparentFallbackToOtherRegion;
     125 
     126     private boolean batchReplication;
     127 
     128     private boolean rateLimiterEnabled = false;
     129 
     130     private boolean rateLimiterThrottleStandardClients = false;
     131 
     132     private Set<String> rateLimiterPrivilegedClients = Collections.emptySet();
     133 
     134     private int rateLimiterBurstSize = 10;
     135 
     136     private int rateLimiterRegistryFetchAverageRate = 500;
     137 
     138     private int rateLimiterFullFetchAverageRate = 100;
     139 
     140     private boolean logIdentityHeaders = true;
     141 
     142     private String listAutoScalingGroupsRoleName = "ListAutoScalingGroups";
     143 
     144     private boolean enableReplicatedRequestCompression = false;
     145 
     146     private String jsonCodecName;
     147 
     148     private String xmlCodecName;
     149 
     150     private int route53BindRebindRetries = 3;
     151 
     152     private int route53BindingRetryIntervalMs = 5 * MINUTES;
     153 
     154     private long route53DomainTTL = 30;
     155 
     156     private AwsBindingStrategy bindingStrategy = AwsBindingStrategy.EIP;
     157 
     158     private int minAvailableInstancesForPeerReplication = -1;
     159 
     160     private int initialCapacityOfResponseCache = 1000;
     161 
     162     private int expectedClientRenewalIntervalSeconds = 30;
     163 
     164     private boolean useAwsAsgApi = true;
     165 
     166     private String myUrl;
     167 
     168     @Override
     169     public boolean shouldEnableSelfPreservation() {
     170         return this.enableSelfPreservation;
     171     }
     172 
     173     @Override
     174     public boolean shouldDisableDelta() {
     175         return this.disableDelta;
     176     }
     177 
     178     @Override
     179     public boolean shouldSyncWhenTimestampDiffers() {
     180         return this.syncWhenTimestampDiffers;
     181     }
     182 
     183     @Override
     184     public boolean shouldPrimeAwsReplicaConnections() {
     185         return this.primeAwsReplicaConnections;
     186     }
     187 
     188     @Override
     189     public boolean shouldDisableDeltaForRemoteRegions() {
     190         return this.disableDeltaForRemoteRegions;
     191     }
     192 
     193     @Override
     194     public boolean shouldGZipContentFromRemoteRegion() {
     195         return this.gZipContentFromRemoteRegion;
     196     }
     197 
     198     @Override
     199     public Set<String> getRemoteRegionAppWhitelist(String regionName) {
     200         return this.remoteRegionAppWhitelist
     201                 .get(regionName == null ? "global" : regionName.trim().toLowerCase());
     202     }
     203 
     204     @Override
     205     public boolean disableTransparentFallbackToOtherRegion() {
     206         return this.disableTransparentFallbackToOtherRegion;
     207     }
     208 
     209     @Override
     210     public boolean shouldBatchReplication() {
     211         return this.batchReplication;
     212     }
     213 
     214     @Override
     215     public String getMyUrl() {
     216         return this.myUrl;
     217     }
     218 
     219     public void setMyUrl(String myUrl) {
     220         this.myUrl = myUrl;
     221     }
     222 
     223     @Override
     224     public boolean shouldLogIdentityHeaders() {
     225         return this.logIdentityHeaders;
     226     }
     227 
     228     @Override
     229     public String getJsonCodecName() {
     230         return this.jsonCodecName;
     231     }
     232 
     233     @Override
     234     public String getXmlCodecName() {
     235         return this.xmlCodecName;
     236     }
     237 
     238     @Override
     239     public boolean shouldUseReadOnlyResponseCache() {
     240         return this.useReadOnlyResponseCache;
     241     }
     242 
     243     @Override
     244     public boolean shouldEnableReplicatedRequestCompression() {
     245         return this.enableReplicatedRequestCompression;
     246     }
     247 
     248     @Override
     249     public String getExperimental(String name) {
     250         if (this.propertyResolver != null) {
     251             return this.propertyResolver.getProperty(PREFIX + ".experimental." + name,
     252                     String.class, null);
     253         }
     254         return null;
     255     }
     256 
     257     @Override
     258     public int getInitialCapacityOfResponseCache() {
     259         return this.initialCapacityOfResponseCache;
     260     }
     261 
     262     public void setInitialCapacityOfResponseCache(int initialCapacityOfResponseCache) {
     263         this.initialCapacityOfResponseCache = initialCapacityOfResponseCache;
     264     }
     265 
     266     @Override
     267     public int getHealthStatusMinNumberOfAvailablePeers() {
     268         return this.minAvailableInstancesForPeerReplication;
     269     }
     270 
     271     public PropertyResolver getPropertyResolver() {
     272         return propertyResolver;
     273     }
     274 
     275     public void setPropertyResolver(PropertyResolver propertyResolver) {
     276         this.propertyResolver = propertyResolver;
     277     }
     278 
     279     public String getAWSAccessId() {
     280         return aWSAccessId;
     281     }
     282 
     283     public void setAWSAccessId(String aWSAccessId) {
     284         this.aWSAccessId = aWSAccessId;
     285     }
     286 
     287     public String getAWSSecretKey() {
     288         return aWSSecretKey;
     289     }
     290 
     291     public void setAWSSecretKey(String aWSSecretKey) {
     292         this.aWSSecretKey = aWSSecretKey;
     293     }
     294 
     295     public int getEIPBindRebindRetries() {
     296         return eIPBindRebindRetries;
     297     }
     298 
     299     public void setEIPBindRebindRetries(int eIPBindRebindRetries) {
     300         this.eIPBindRebindRetries = eIPBindRebindRetries;
     301     }
     302 
     303     public int getEIPBindingRetryIntervalMs() {
     304         return eIPBindingRetryIntervalMs;
     305     }
     306 
     307     public void setEIPBindingRetryIntervalMs(int eIPBindingRetryIntervalMs) {
     308         this.eIPBindingRetryIntervalMs = eIPBindingRetryIntervalMs;
     309     }
     310 
     311     public int getEIPBindingRetryIntervalMsWhenUnbound() {
     312         return eIPBindingRetryIntervalMsWhenUnbound;
     313     }
     314 
     315     public void setEIPBindingRetryIntervalMsWhenUnbound(
     316             int eIPBindingRetryIntervalMsWhenUnbound) {
     317         this.eIPBindingRetryIntervalMsWhenUnbound = eIPBindingRetryIntervalMsWhenUnbound;
     318     }
     319 
     320     public boolean isEnableSelfPreservation() {
     321         return enableSelfPreservation;
     322     }
     323 
     324     public void setEnableSelfPreservation(boolean enableSelfPreservation) {
     325         this.enableSelfPreservation = enableSelfPreservation;
     326     }
     327 
     328     @Override
     329     public double getRenewalPercentThreshold() {
     330         return renewalPercentThreshold;
     331     }
     332 
     333     public void setRenewalPercentThreshold(double renewalPercentThreshold) {
     334         this.renewalPercentThreshold = renewalPercentThreshold;
     335     }
     336 
     337     @Override
     338     public int getRenewalThresholdUpdateIntervalMs() {
     339         return renewalThresholdUpdateIntervalMs;
     340     }
     341 
     342     @Override
     343     public int getExpectedClientRenewalIntervalSeconds() {
     344         return this.expectedClientRenewalIntervalSeconds;
     345     }
     346 
     347     public void setExpectedClientRenewalIntervalSeconds(
     348             int expectedClientRenewalIntervalSeconds) {
     349         this.expectedClientRenewalIntervalSeconds = expectedClientRenewalIntervalSeconds;
     350     }
     351 
     352     public void setRenewalThresholdUpdateIntervalMs(
     353             int renewalThresholdUpdateIntervalMs) {
     354         this.renewalThresholdUpdateIntervalMs = renewalThresholdUpdateIntervalMs;
     355     }
     356 
     357     @Override
     358     public int getPeerEurekaNodesUpdateIntervalMs() {
     359         return peerEurekaNodesUpdateIntervalMs;
     360     }
     361 
     362     public void setPeerEurekaNodesUpdateIntervalMs(int peerEurekaNodesUpdateIntervalMs) {
     363         this.peerEurekaNodesUpdateIntervalMs = peerEurekaNodesUpdateIntervalMs;
     364     }
     365 
     366     @Override
     367     public int getNumberOfReplicationRetries() {
     368         return numberOfReplicationRetries;
     369     }
     370 
     371     public void setNumberOfReplicationRetries(int numberOfReplicationRetries) {
     372         this.numberOfReplicationRetries = numberOfReplicationRetries;
     373     }
     374 
     375     @Override
     376     public int getPeerEurekaStatusRefreshTimeIntervalMs() {
     377         return peerEurekaStatusRefreshTimeIntervalMs;
     378     }
     379 
     380     public void setPeerEurekaStatusRefreshTimeIntervalMs(
     381             int peerEurekaStatusRefreshTimeIntervalMs) {
     382         this.peerEurekaStatusRefreshTimeIntervalMs = peerEurekaStatusRefreshTimeIntervalMs;
     383     }
     384 
     385     @Override
     386     public int getWaitTimeInMsWhenSyncEmpty() {
     387         return waitTimeInMsWhenSyncEmpty;
     388     }
     389 
     390     public void setWaitTimeInMsWhenSyncEmpty(int waitTimeInMsWhenSyncEmpty) {
     391         this.waitTimeInMsWhenSyncEmpty = waitTimeInMsWhenSyncEmpty;
     392     }
     393 
     394     @Override
     395     public int getPeerNodeConnectTimeoutMs() {
     396         return peerNodeConnectTimeoutMs;
     397     }
     398 
     399     public void setPeerNodeConnectTimeoutMs(int peerNodeConnectTimeoutMs) {
     400         this.peerNodeConnectTimeoutMs = peerNodeConnectTimeoutMs;
     401     }
     402 
     403     @Override
     404     public int getPeerNodeReadTimeoutMs() {
     405         return peerNodeReadTimeoutMs;
     406     }
     407 
     408     public void setPeerNodeReadTimeoutMs(int peerNodeReadTimeoutMs) {
     409         this.peerNodeReadTimeoutMs = peerNodeReadTimeoutMs;
     410     }
     411 
     412     @Override
     413     public int getPeerNodeTotalConnections() {
     414         return peerNodeTotalConnections;
     415     }
     416 
     417     public void setPeerNodeTotalConnections(int peerNodeTotalConnections) {
     418         this.peerNodeTotalConnections = peerNodeTotalConnections;
     419     }
     420 
     421     @Override
     422     public int getPeerNodeTotalConnectionsPerHost() {
     423         return peerNodeTotalConnectionsPerHost;
     424     }
     425 
     426     public void setPeerNodeTotalConnectionsPerHost(int peerNodeTotalConnectionsPerHost) {
     427         this.peerNodeTotalConnectionsPerHost = peerNodeTotalConnectionsPerHost;
     428     }
     429 
     430     @Override
     431     public int getPeerNodeConnectionIdleTimeoutSeconds() {
     432         return peerNodeConnectionIdleTimeoutSeconds;
     433     }
     434 
     435     public void setPeerNodeConnectionIdleTimeoutSeconds(
     436             int peerNodeConnectionIdleTimeoutSeconds) {
     437         this.peerNodeConnectionIdleTimeoutSeconds = peerNodeConnectionIdleTimeoutSeconds;
     438     }
     439 
     440     @Override
     441     public long getRetentionTimeInMSInDeltaQueue() {
     442         return retentionTimeInMSInDeltaQueue;
     443     }
     444 
     445     public void setRetentionTimeInMSInDeltaQueue(long retentionTimeInMSInDeltaQueue) {
     446         this.retentionTimeInMSInDeltaQueue = retentionTimeInMSInDeltaQueue;
     447     }
     448 
     449     @Override
     450     public long getDeltaRetentionTimerIntervalInMs() {
     451         return deltaRetentionTimerIntervalInMs;
     452     }
     453 
     454     public void setDeltaRetentionTimerIntervalInMs(long deltaRetentionTimerIntervalInMs) {
     455         this.deltaRetentionTimerIntervalInMs = deltaRetentionTimerIntervalInMs;
     456     }
     457 
     458     @Override
     459     public long getEvictionIntervalTimerInMs() {
     460         return evictionIntervalTimerInMs;
     461     }
     462 
     463     @Override
     464     public boolean shouldUseAwsAsgApi() {
     465         return this.useAwsAsgApi;
     466     }
     467 
     468     public void setUseAwsAsgApi(boolean useAwsAsgApi) {
     469         this.useAwsAsgApi = useAwsAsgApi;
     470     }
     471 
     472     public void setEvictionIntervalTimerInMs(long evictionIntervalTimerInMs) {
     473         this.evictionIntervalTimerInMs = evictionIntervalTimerInMs;
     474     }
     475 
     476     public int getASGQueryTimeoutMs() {
     477         return aSGQueryTimeoutMs;
     478     }
     479 
     480     public void setASGQueryTimeoutMs(int aSGQueryTimeoutMs) {
     481         this.aSGQueryTimeoutMs = aSGQueryTimeoutMs;
     482     }
     483 
     484     public long getASGUpdateIntervalMs() {
     485         return aSGUpdateIntervalMs;
     486     }
     487 
     488     public void setASGUpdateIntervalMs(long aSGUpdateIntervalMs) {
     489         this.aSGUpdateIntervalMs = aSGUpdateIntervalMs;
     490     }
     491 
     492     public long getASGCacheExpiryTimeoutMs() {
     493         return aSGCacheExpiryTimeoutMs;
     494     }
     495 
     496     public void setASGCacheExpiryTimeoutMs(long aSGCacheExpiryTimeoutMs) {
     497         this.aSGCacheExpiryTimeoutMs = aSGCacheExpiryTimeoutMs;
     498     }
     499 
     500     @Override
     501     public long getResponseCacheAutoExpirationInSeconds() {
     502         return responseCacheAutoExpirationInSeconds;
     503     }
     504 
     505     public void setResponseCacheAutoExpirationInSeconds(
     506             long responseCacheAutoExpirationInSeconds) {
     507         this.responseCacheAutoExpirationInSeconds = responseCacheAutoExpirationInSeconds;
     508     }
     509 
     510     @Override
     511     public long getResponseCacheUpdateIntervalMs() {
     512         return responseCacheUpdateIntervalMs;
     513     }
     514 
     515     public void setResponseCacheUpdateIntervalMs(long responseCacheUpdateIntervalMs) {
     516         this.responseCacheUpdateIntervalMs = responseCacheUpdateIntervalMs;
     517     }
     518 
     519     public boolean isUseReadOnlyResponseCache() {
     520         return useReadOnlyResponseCache;
     521     }
     522 
     523     public void setUseReadOnlyResponseCache(boolean useReadOnlyResponseCache) {
     524         this.useReadOnlyResponseCache = useReadOnlyResponseCache;
     525     }
     526 
     527     public boolean isDisableDelta() {
     528         return disableDelta;
     529     }
     530 
     531     public void setDisableDelta(boolean disableDelta) {
     532         this.disableDelta = disableDelta;
     533     }
     534 
     535     @Override
     536     public long getMaxIdleThreadInMinutesAgeForStatusReplication() {
     537         return maxIdleThreadInMinutesAgeForStatusReplication;
     538     }
     539 
     540     public void setMaxIdleThreadInMinutesAgeForStatusReplication(
     541             long maxIdleThreadInMinutesAgeForStatusReplication) {
     542         this.maxIdleThreadInMinutesAgeForStatusReplication = maxIdleThreadInMinutesAgeForStatusReplication;
     543     }
     544 
     545     @Override
     546     public int getMinThreadsForStatusReplication() {
     547         return minThreadsForStatusReplication;
     548     }
     549 
     550     public void setMinThreadsForStatusReplication(int minThreadsForStatusReplication) {
     551         this.minThreadsForStatusReplication = minThreadsForStatusReplication;
     552     }
     553 
     554     @Override
     555     public int getMaxThreadsForStatusReplication() {
     556         return maxThreadsForStatusReplication;
     557     }
     558 
     559     public void setMaxThreadsForStatusReplication(int maxThreadsForStatusReplication) {
     560         this.maxThreadsForStatusReplication = maxThreadsForStatusReplication;
     561     }
     562 
     563     @Override
     564     public int getMaxElementsInStatusReplicationPool() {
     565         return maxElementsInStatusReplicationPool;
     566     }
     567 
     568     public void setMaxElementsInStatusReplicationPool(
     569             int maxElementsInStatusReplicationPool) {
     570         this.maxElementsInStatusReplicationPool = maxElementsInStatusReplicationPool;
     571     }
     572 
     573     public boolean isSyncWhenTimestampDiffers() {
     574         return syncWhenTimestampDiffers;
     575     }
     576 
     577     public void setSyncWhenTimestampDiffers(boolean syncWhenTimestampDiffers) {
     578         this.syncWhenTimestampDiffers = syncWhenTimestampDiffers;
     579     }
     580 
     581     @Override
     582     public int getRegistrySyncRetries() {
     583         return registrySyncRetries;
     584     }
     585 
     586     public void setRegistrySyncRetries(int registrySyncRetries) {
     587         this.registrySyncRetries = registrySyncRetries;
     588     }
     589 
     590     @Override
     591     public long getRegistrySyncRetryWaitMs() {
     592         return registrySyncRetryWaitMs;
     593     }
     594 
     595     public void setRegistrySyncRetryWaitMs(long registrySyncRetryWaitMs) {
     596         this.registrySyncRetryWaitMs = registrySyncRetryWaitMs;
     597     }
     598 
     599     @Override
     600     public int getMaxElementsInPeerReplicationPool() {
     601         return maxElementsInPeerReplicationPool;
     602     }
     603 
     604     public void setMaxElementsInPeerReplicationPool(
     605             int maxElementsInPeerReplicationPool) {
     606         this.maxElementsInPeerReplicationPool = maxElementsInPeerReplicationPool;
     607     }
     608 
     609     @Override
     610     public long getMaxIdleThreadAgeInMinutesForPeerReplication() {
     611         return maxIdleThreadAgeInMinutesForPeerReplication;
     612     }
     613 
     614     public void setMaxIdleThreadAgeInMinutesForPeerReplication(
     615             long maxIdleThreadAgeInMinutesForPeerReplication) {
     616         this.maxIdleThreadAgeInMinutesForPeerReplication = maxIdleThreadAgeInMinutesForPeerReplication;
     617     }
     618 
     619     @Override
     620     public int getMinThreadsForPeerReplication() {
     621         return minThreadsForPeerReplication;
     622     }
     623 
     624     public void setMinThreadsForPeerReplication(int minThreadsForPeerReplication) {
     625         this.minThreadsForPeerReplication = minThreadsForPeerReplication;
     626     }
     627 
     628     @Override
     629     public int getMaxThreadsForPeerReplication() {
     630         return maxThreadsForPeerReplication;
     631     }
     632 
     633     public void setMaxThreadsForPeerReplication(int maxThreadsForPeerReplication) {
     634         this.maxThreadsForPeerReplication = maxThreadsForPeerReplication;
     635     }
     636 
     637     @Override
     638     public int getMaxTimeForReplication() {
     639         return maxTimeForReplication;
     640     }
     641 
     642     public void setMaxTimeForReplication(int maxTimeForReplication) {
     643         this.maxTimeForReplication = maxTimeForReplication;
     644     }
     645 
     646     public boolean isPrimeAwsReplicaConnections() {
     647         return primeAwsReplicaConnections;
     648     }
     649 
     650     public void setPrimeAwsReplicaConnections(boolean primeAwsReplicaConnections) {
     651         this.primeAwsReplicaConnections = primeAwsReplicaConnections;
     652     }
     653 
     654     public boolean isDisableDeltaForRemoteRegions() {
     655         return disableDeltaForRemoteRegions;
     656     }
     657 
     658     public void setDisableDeltaForRemoteRegions(boolean disableDeltaForRemoteRegions) {
     659         this.disableDeltaForRemoteRegions = disableDeltaForRemoteRegions;
     660     }
     661 
     662     @Override
     663     public int getRemoteRegionConnectTimeoutMs() {
     664         return remoteRegionConnectTimeoutMs;
     665     }
     666 
     667     public void setRemoteRegionConnectTimeoutMs(int remoteRegionConnectTimeoutMs) {
     668         this.remoteRegionConnectTimeoutMs = remoteRegionConnectTimeoutMs;
     669     }
     670 
     671     @Override
     672     public int getRemoteRegionReadTimeoutMs() {
     673         return remoteRegionReadTimeoutMs;
     674     }
     675 
     676     public void setRemoteRegionReadTimeoutMs(int remoteRegionReadTimeoutMs) {
     677         this.remoteRegionReadTimeoutMs = remoteRegionReadTimeoutMs;
     678     }
     679 
     680     @Override
     681     public int getRemoteRegionTotalConnections() {
     682         return remoteRegionTotalConnections;
     683     }
     684 
     685     public void setRemoteRegionTotalConnections(int remoteRegionTotalConnections) {
     686         this.remoteRegionTotalConnections = remoteRegionTotalConnections;
     687     }
     688 
     689     @Override
     690     public int getRemoteRegionTotalConnectionsPerHost() {
     691         return remoteRegionTotalConnectionsPerHost;
     692     }
     693 
     694     public void setRemoteRegionTotalConnectionsPerHost(
     695             int remoteRegionTotalConnectionsPerHost) {
     696         this.remoteRegionTotalConnectionsPerHost = remoteRegionTotalConnectionsPerHost;
     697     }
     698 
     699     @Override
     700     public int getRemoteRegionConnectionIdleTimeoutSeconds() {
     701         return remoteRegionConnectionIdleTimeoutSeconds;
     702     }
     703 
     704     public void setRemoteRegionConnectionIdleTimeoutSeconds(
     705             int remoteRegionConnectionIdleTimeoutSeconds) {
     706         this.remoteRegionConnectionIdleTimeoutSeconds = remoteRegionConnectionIdleTimeoutSeconds;
     707     }
     708 
     709     public boolean isgZipContentFromRemoteRegion() {
     710         return gZipContentFromRemoteRegion;
     711     }
     712 
     713     public void setgZipContentFromRemoteRegion(boolean gZipContentFromRemoteRegion) {
     714         this.gZipContentFromRemoteRegion = gZipContentFromRemoteRegion;
     715     }
     716 
     717     @Override
     718     public Map<String, String> getRemoteRegionUrlsWithName() {
     719         return remoteRegionUrlsWithName;
     720     }
     721 
     722     public void setRemoteRegionUrlsWithName(
     723             Map<String, String> remoteRegionUrlsWithName) {
     724         this.remoteRegionUrlsWithName = remoteRegionUrlsWithName;
     725     }
     726 
     727     @Override
     728     public String[] getRemoteRegionUrls() {
     729         return remoteRegionUrls;
     730     }
     731 
     732     public void setRemoteRegionUrls(String[] remoteRegionUrls) {
     733         this.remoteRegionUrls = remoteRegionUrls;
     734     }
     735 
     736     public Map<String, Set<String>> getRemoteRegionAppWhitelist() {
     737         return remoteRegionAppWhitelist;
     738     }
     739 
     740     public void setRemoteRegionAppWhitelist(
     741             Map<String, Set<String>> remoteRegionAppWhitelist) {
     742         this.remoteRegionAppWhitelist = remoteRegionAppWhitelist;
     743     }
     744 
     745     @Override
     746     public int getRemoteRegionRegistryFetchInterval() {
     747         return remoteRegionRegistryFetchInterval;
     748     }
     749 
     750     public void setRemoteRegionRegistryFetchInterval(
     751             int remoteRegionRegistryFetchInterval) {
     752         this.remoteRegionRegistryFetchInterval = remoteRegionRegistryFetchInterval;
     753     }
     754 
     755     @Override
     756     public int getRemoteRegionFetchThreadPoolSize() {
     757         return remoteRegionFetchThreadPoolSize;
     758     }
     759 
     760     public void setRemoteRegionFetchThreadPoolSize(int remoteRegionFetchThreadPoolSize) {
     761         this.remoteRegionFetchThreadPoolSize = remoteRegionFetchThreadPoolSize;
     762     }
     763 
     764     @Override
     765     public String getRemoteRegionTrustStore() {
     766         return remoteRegionTrustStore;
     767     }
     768 
     769     public void setRemoteRegionTrustStore(String remoteRegionTrustStore) {
     770         this.remoteRegionTrustStore = remoteRegionTrustStore;
     771     }
     772 
     773     @Override
     774     public String getRemoteRegionTrustStorePassword() {
     775         return remoteRegionTrustStorePassword;
     776     }
     777 
     778     public void setRemoteRegionTrustStorePassword(String remoteRegionTrustStorePassword) {
     779         this.remoteRegionTrustStorePassword = remoteRegionTrustStorePassword;
     780     }
     781 
     782     public boolean isDisableTransparentFallbackToOtherRegion() {
     783         return disableTransparentFallbackToOtherRegion;
     784     }
     785 
     786     public void setDisableTransparentFallbackToOtherRegion(
     787             boolean disableTransparentFallbackToOtherRegion) {
     788         this.disableTransparentFallbackToOtherRegion = disableTransparentFallbackToOtherRegion;
     789     }
     790 
     791     public boolean isBatchReplication() {
     792         return batchReplication;
     793     }
     794 
     795     public void setBatchReplication(boolean batchReplication) {
     796         this.batchReplication = batchReplication;
     797     }
     798 
     799     @Override
     800     public boolean isRateLimiterEnabled() {
     801         return rateLimiterEnabled;
     802     }
     803 
     804     public void setRateLimiterEnabled(boolean rateLimiterEnabled) {
     805         this.rateLimiterEnabled = rateLimiterEnabled;
     806     }
     807 
     808     @Override
     809     public boolean isRateLimiterThrottleStandardClients() {
     810         return rateLimiterThrottleStandardClients;
     811     }
     812 
     813     public void setRateLimiterThrottleStandardClients(
     814             boolean rateLimiterThrottleStandardClients) {
     815         this.rateLimiterThrottleStandardClients = rateLimiterThrottleStandardClients;
     816     }
     817 
     818     @Override
     819     public Set<String> getRateLimiterPrivilegedClients() {
     820         return rateLimiterPrivilegedClients;
     821     }
     822 
     823     public void setRateLimiterPrivilegedClients(
     824             Set<String> rateLimiterPrivilegedClients) {
     825         this.rateLimiterPrivilegedClients = rateLimiterPrivilegedClients;
     826     }
     827 
     828     @Override
     829     public int getRateLimiterBurstSize() {
     830         return rateLimiterBurstSize;
     831     }
     832 
     833     public void setRateLimiterBurstSize(int rateLimiterBurstSize) {
     834         this.rateLimiterBurstSize = rateLimiterBurstSize;
     835     }
     836 
     837     @Override
     838     public int getRateLimiterRegistryFetchAverageRate() {
     839         return rateLimiterRegistryFetchAverageRate;
     840     }
     841 
     842     public void setRateLimiterRegistryFetchAverageRate(
     843             int rateLimiterRegistryFetchAverageRate) {
     844         this.rateLimiterRegistryFetchAverageRate = rateLimiterRegistryFetchAverageRate;
     845     }
     846 
     847     @Override
     848     public int getRateLimiterFullFetchAverageRate() {
     849         return rateLimiterFullFetchAverageRate;
     850     }
     851 
     852     public void setRateLimiterFullFetchAverageRate(int rateLimiterFullFetchAverageRate) {
     853         this.rateLimiterFullFetchAverageRate = rateLimiterFullFetchAverageRate;
     854     }
     855 
     856     public boolean isLogIdentityHeaders() {
     857         return logIdentityHeaders;
     858     }
     859 
     860     public void setLogIdentityHeaders(boolean logIdentityHeaders) {
     861         this.logIdentityHeaders = logIdentityHeaders;
     862     }
     863 
     864     @Override
     865     public String getListAutoScalingGroupsRoleName() {
     866         return listAutoScalingGroupsRoleName;
     867     }
     868 
     869     public void setListAutoScalingGroupsRoleName(String listAutoScalingGroupsRoleName) {
     870         this.listAutoScalingGroupsRoleName = listAutoScalingGroupsRoleName;
     871     }
     872 
     873     public boolean isEnableReplicatedRequestCompression() {
     874         return enableReplicatedRequestCompression;
     875     }
     876 
     877     public void setEnableReplicatedRequestCompression(
     878             boolean enableReplicatedRequestCompression) {
     879         this.enableReplicatedRequestCompression = enableReplicatedRequestCompression;
     880     }
     881 
     882     public void setJsonCodecName(String jsonCodecName) {
     883         this.jsonCodecName = jsonCodecName;
     884     }
     885 
     886     public void setXmlCodecName(String xmlCodecName) {
     887         this.xmlCodecName = xmlCodecName;
     888     }
     889 
     890     @Override
     891     public int getRoute53BindRebindRetries() {
     892         return route53BindRebindRetries;
     893     }
     894 
     895     public void setRoute53BindRebindRetries(int route53BindRebindRetries) {
     896         this.route53BindRebindRetries = route53BindRebindRetries;
     897     }
     898 
     899     @Override
     900     public int getRoute53BindingRetryIntervalMs() {
     901         return route53BindingRetryIntervalMs;
     902     }
     903 
     904     public void setRoute53BindingRetryIntervalMs(int route53BindingRetryIntervalMs) {
     905         this.route53BindingRetryIntervalMs = route53BindingRetryIntervalMs;
     906     }
     907 
     908     @Override
     909     public long getRoute53DomainTTL() {
     910         return route53DomainTTL;
     911     }
     912 
     913     public void setRoute53DomainTTL(long route53DomainTTL) {
     914         this.route53DomainTTL = route53DomainTTL;
     915     }
     916 
     917     @Override
     918     public AwsBindingStrategy getBindingStrategy() {
     919         return bindingStrategy;
     920     }
     921 
     922     public void setBindingStrategy(AwsBindingStrategy bindingStrategy) {
     923         this.bindingStrategy = bindingStrategy;
     924     }
     925 
     926     public int getMinAvailableInstancesForPeerReplication() {
     927         return minAvailableInstancesForPeerReplication;
     928     }
     929 
     930     public void setMinAvailableInstancesForPeerReplication(
     931             int minAvailableInstancesForPeerReplication) {
     932         this.minAvailableInstancesForPeerReplication = minAvailableInstancesForPeerReplication;
     933     }
     934 
     935     @Override
     936     public boolean equals(Object o) {
     937         if (this == o) {
     938             return true;
     939         }
     940         if (o == null || getClass() != o.getClass()) {
     941             return false;
     942         }
     943         EurekaServerConfigBean that = (EurekaServerConfigBean) o;
     944         return aSGCacheExpiryTimeoutMs == that.aSGCacheExpiryTimeoutMs
     945                 && aSGQueryTimeoutMs == that.aSGQueryTimeoutMs
     946                 && aSGUpdateIntervalMs == that.aSGUpdateIntervalMs
     947                 && Objects.equals(aWSAccessId, that.aWSAccessId)
     948                 && Objects.equals(aWSSecretKey, that.aWSSecretKey)
     949                 && batchReplication == that.batchReplication
     950                 && bindingStrategy == that.bindingStrategy
     951                 && deltaRetentionTimerIntervalInMs == that.deltaRetentionTimerIntervalInMs
     952                 && disableDelta == that.disableDelta
     953                 && disableDeltaForRemoteRegions == that.disableDeltaForRemoteRegions
     954                 && disableTransparentFallbackToOtherRegion == that.disableTransparentFallbackToOtherRegion
     955                 && eIPBindingRetryIntervalMs == that.eIPBindingRetryIntervalMs
     956                 && eIPBindingRetryIntervalMsWhenUnbound == that.eIPBindingRetryIntervalMsWhenUnbound
     957                 && eIPBindRebindRetries == that.eIPBindRebindRetries
     958                 && enableReplicatedRequestCompression == that.enableReplicatedRequestCompression
     959                 && enableSelfPreservation == that.enableSelfPreservation
     960                 && evictionIntervalTimerInMs == that.evictionIntervalTimerInMs
     961                 && gZipContentFromRemoteRegion == that.gZipContentFromRemoteRegion
     962                 && Objects.equals(jsonCodecName, that.jsonCodecName)
     963                 && Objects.equals(listAutoScalingGroupsRoleName,
     964                         that.listAutoScalingGroupsRoleName)
     965                 && logIdentityHeaders == that.logIdentityHeaders
     966                 && maxElementsInPeerReplicationPool == that.maxElementsInPeerReplicationPool
     967                 && maxElementsInStatusReplicationPool == that.maxElementsInStatusReplicationPool
     968                 && maxIdleThreadAgeInMinutesForPeerReplication == that.maxIdleThreadAgeInMinutesForPeerReplication
     969                 && maxIdleThreadInMinutesAgeForStatusReplication == that.maxIdleThreadInMinutesAgeForStatusReplication
     970                 && maxThreadsForPeerReplication == that.maxThreadsForPeerReplication
     971                 && maxThreadsForStatusReplication == that.maxThreadsForStatusReplication
     972                 && maxTimeForReplication == that.maxTimeForReplication
     973                 && minAvailableInstancesForPeerReplication == that.minAvailableInstancesForPeerReplication
     974                 && minThreadsForPeerReplication == that.minThreadsForPeerReplication
     975                 && minThreadsForStatusReplication == that.minThreadsForStatusReplication
     976                 && numberOfReplicationRetries == that.numberOfReplicationRetries
     977                 && peerEurekaNodesUpdateIntervalMs == that.peerEurekaNodesUpdateIntervalMs
     978                 && peerEurekaStatusRefreshTimeIntervalMs == that.peerEurekaStatusRefreshTimeIntervalMs
     979                 && peerNodeConnectionIdleTimeoutSeconds == that.peerNodeConnectionIdleTimeoutSeconds
     980                 && peerNodeConnectTimeoutMs == that.peerNodeConnectTimeoutMs
     981                 && peerNodeReadTimeoutMs == that.peerNodeReadTimeoutMs
     982                 && peerNodeTotalConnections == that.peerNodeTotalConnections
     983                 && peerNodeTotalConnectionsPerHost == that.peerNodeTotalConnectionsPerHost
     984                 && primeAwsReplicaConnections == that.primeAwsReplicaConnections
     985                 && Objects.equals(propertyResolver, that.propertyResolver)
     986                 && rateLimiterBurstSize == that.rateLimiterBurstSize
     987                 && rateLimiterEnabled == that.rateLimiterEnabled
     988                 && rateLimiterFullFetchAverageRate == that.rateLimiterFullFetchAverageRate
     989                 && Objects.equals(rateLimiterPrivilegedClients,
     990                         that.rateLimiterPrivilegedClients)
     991                 && rateLimiterRegistryFetchAverageRate == that.rateLimiterRegistryFetchAverageRate
     992                 && rateLimiterThrottleStandardClients == that.rateLimiterThrottleStandardClients
     993                 && registrySyncRetries == that.registrySyncRetries
     994                 && registrySyncRetryWaitMs == that.registrySyncRetryWaitMs
     995                 && Objects.equals(remoteRegionAppWhitelist, that.remoteRegionAppWhitelist)
     996                 && remoteRegionConnectionIdleTimeoutSeconds == that.remoteRegionConnectionIdleTimeoutSeconds
     997                 && remoteRegionConnectTimeoutMs == that.remoteRegionConnectTimeoutMs
     998                 && remoteRegionFetchThreadPoolSize == that.remoteRegionFetchThreadPoolSize
     999                 && remoteRegionReadTimeoutMs == that.remoteRegionReadTimeoutMs
    1000                 && remoteRegionRegistryFetchInterval == that.remoteRegionRegistryFetchInterval
    1001                 && remoteRegionTotalConnections == that.remoteRegionTotalConnections
    1002                 && remoteRegionTotalConnectionsPerHost == that.remoteRegionTotalConnectionsPerHost
    1003                 && Objects.equals(remoteRegionTrustStore, that.remoteRegionTrustStore)
    1004                 && Objects.equals(remoteRegionTrustStorePassword,
    1005                         that.remoteRegionTrustStorePassword)
    1006                 && Arrays.equals(remoteRegionUrls, that.remoteRegionUrls)
    1007                 && Objects.equals(remoteRegionUrlsWithName, that.remoteRegionUrlsWithName)
    1008                 && Double.compare(that.renewalPercentThreshold,
    1009                         renewalPercentThreshold) == 0
    1010                 && renewalThresholdUpdateIntervalMs == that.renewalThresholdUpdateIntervalMs
    1011                 && responseCacheAutoExpirationInSeconds == that.responseCacheAutoExpirationInSeconds
    1012                 && responseCacheUpdateIntervalMs == that.responseCacheUpdateIntervalMs
    1013                 && retentionTimeInMSInDeltaQueue == that.retentionTimeInMSInDeltaQueue
    1014                 && route53BindingRetryIntervalMs == that.route53BindingRetryIntervalMs
    1015                 && route53BindRebindRetries == that.route53BindRebindRetries
    1016                 && route53DomainTTL == that.route53DomainTTL
    1017                 && syncWhenTimestampDiffers == that.syncWhenTimestampDiffers
    1018                 && useReadOnlyResponseCache == that.useReadOnlyResponseCache
    1019                 && waitTimeInMsWhenSyncEmpty == that.waitTimeInMsWhenSyncEmpty
    1020                 && Objects.equals(xmlCodecName, that.xmlCodecName)
    1021                 && initialCapacityOfResponseCache == that.initialCapacityOfResponseCache
    1022                 && expectedClientRenewalIntervalSeconds == that.expectedClientRenewalIntervalSeconds
    1023                 && useAwsAsgApi == that.useAwsAsgApi && Objects.equals(myUrl, that.myUrl);
    1024     }
    1025 
    1026     @Override
    1027     public int hashCode() {
    1028         return Objects.hash(aSGCacheExpiryTimeoutMs, aSGQueryTimeoutMs,
    1029                 aSGUpdateIntervalMs, aWSAccessId, aWSSecretKey, batchReplication,
    1030                 bindingStrategy, deltaRetentionTimerIntervalInMs, disableDelta,
    1031                 disableDeltaForRemoteRegions, disableTransparentFallbackToOtherRegion,
    1032                 eIPBindRebindRetries, eIPBindingRetryIntervalMs,
    1033                 eIPBindingRetryIntervalMsWhenUnbound, enableReplicatedRequestCompression,
    1034                 enableSelfPreservation, evictionIntervalTimerInMs,
    1035                 gZipContentFromRemoteRegion, jsonCodecName, listAutoScalingGroupsRoleName,
    1036                 logIdentityHeaders, maxElementsInPeerReplicationPool,
    1037                 maxElementsInStatusReplicationPool,
    1038                 maxIdleThreadAgeInMinutesForPeerReplication,
    1039                 maxIdleThreadInMinutesAgeForStatusReplication,
    1040                 maxThreadsForPeerReplication, maxThreadsForStatusReplication,
    1041                 maxTimeForReplication, minAvailableInstancesForPeerReplication,
    1042                 minThreadsForPeerReplication, minThreadsForStatusReplication,
    1043                 numberOfReplicationRetries, peerEurekaNodesUpdateIntervalMs,
    1044                 peerEurekaStatusRefreshTimeIntervalMs, peerNodeConnectTimeoutMs,
    1045                 peerNodeConnectionIdleTimeoutSeconds, peerNodeReadTimeoutMs,
    1046                 peerNodeTotalConnections, peerNodeTotalConnectionsPerHost,
    1047                 primeAwsReplicaConnections, propertyResolver, rateLimiterBurstSize,
    1048                 rateLimiterEnabled, rateLimiterFullFetchAverageRate,
    1049                 rateLimiterPrivilegedClients, rateLimiterRegistryFetchAverageRate,
    1050                 rateLimiterThrottleStandardClients, registrySyncRetries,
    1051                 registrySyncRetryWaitMs, remoteRegionAppWhitelist,
    1052                 remoteRegionConnectTimeoutMs, remoteRegionConnectionIdleTimeoutSeconds,
    1053                 remoteRegionFetchThreadPoolSize, remoteRegionReadTimeoutMs,
    1054                 remoteRegionRegistryFetchInterval, remoteRegionTotalConnections,
    1055                 remoteRegionTotalConnectionsPerHost, remoteRegionTrustStore,
    1056                 remoteRegionTrustStorePassword, remoteRegionUrls,
    1057                 remoteRegionUrlsWithName, renewalPercentThreshold,
    1058                 renewalThresholdUpdateIntervalMs, responseCacheAutoExpirationInSeconds,
    1059                 responseCacheUpdateIntervalMs, retentionTimeInMSInDeltaQueue,
    1060                 route53BindRebindRetries, route53BindingRetryIntervalMs, route53DomainTTL,
    1061                 syncWhenTimestampDiffers, useReadOnlyResponseCache,
    1062                 waitTimeInMsWhenSyncEmpty, xmlCodecName, initialCapacityOfResponseCache,
    1063                 expectedClientRenewalIntervalSeconds, useAwsAsgApi, myUrl);
    1064     }
    1065 
    1066     @Override
    1067     public String toString() {
    1068         return new ToStringCreator(this)
    1069                 .append("aSGCacheExpiryTimeoutMs", this.aSGCacheExpiryTimeoutMs)
    1070                 .append("aSGQueryTimeoutMs", this.aSGQueryTimeoutMs)
    1071                 .append("aSGUpdateIntervalMs", this.aSGUpdateIntervalMs)
    1072                 .append("aWSAccessId", this.aWSAccessId)
    1073                 .append("aWSSecretKey", this.aWSSecretKey)
    1074                 .append("batchReplication", this.batchReplication)
    1075                 .append("bindingStrategy", this.bindingStrategy)
    1076                 .append("deltaRetentionTimerIntervalInMs",
    1077                         this.deltaRetentionTimerIntervalInMs)
    1078                 .append("disableDelta", this.disableDelta)
    1079                 .append("disableDeltaForRemoteRegions", this.disableDeltaForRemoteRegions)
    1080                 .append("disableTransparentFallbackToOtherRegion",
    1081                         this.disableTransparentFallbackToOtherRegion)
    1082                 .append("eIPBindRebindRetries", this.eIPBindRebindRetries)
    1083                 .append("eIPBindingRetryIntervalMs", this.eIPBindingRetryIntervalMs)
    1084                 .append("eIPBindingRetryIntervalMsWhenUnbound",
    1085                         this.eIPBindingRetryIntervalMsWhenUnbound)
    1086                 .append("enableReplicatedRequestCompression",
    1087                         this.enableReplicatedRequestCompression)
    1088                 .append("enableSelfPreservation", this.enableSelfPreservation)
    1089                 .append("evictionIntervalTimerInMs", this.evictionIntervalTimerInMs)
    1090                 .append("gZipContentFromRemoteRegion", this.gZipContentFromRemoteRegion)
    1091                 .append("jsonCodecName", this.jsonCodecName)
    1092                 .append("listAutoScalingGroupsRoleName",
    1093                         this.listAutoScalingGroupsRoleName)
    1094                 .append("logIdentityHeaders", this.logIdentityHeaders)
    1095                 .append("maxElementsInPeerReplicationPool",
    1096                         this.maxElementsInPeerReplicationPool)
    1097                 .append("maxElementsInStatusReplicationPool",
    1098                         this.maxElementsInStatusReplicationPool)
    1099                 .append("maxIdleThreadAgeInMinutesForPeerReplication",
    1100                         this.maxIdleThreadAgeInMinutesForPeerReplication)
    1101                 .append("maxIdleThreadInMinutesAgeForStatusReplication",
    1102                         this.maxIdleThreadInMinutesAgeForStatusReplication)
    1103                 .append("maxThreadsForPeerReplication", this.maxThreadsForPeerReplication)
    1104                 .append("maxThreadsForStatusReplication",
    1105                         this.maxThreadsForStatusReplication)
    1106                 .append("maxTimeForReplication", this.maxTimeForReplication)
    1107                 .append("minAvailableInstancesForPeerReplication",
    1108                         this.minAvailableInstancesForPeerReplication)
    1109                 .append("minThreadsForPeerReplication", this.minThreadsForPeerReplication)
    1110                 .append("minThreadsForStatusReplication",
    1111                         this.minThreadsForStatusReplication)
    1112                 .append("numberOfReplicationRetries", this.numberOfReplicationRetries)
    1113                 .append("peerEurekaNodesUpdateIntervalMs",
    1114                         this.peerEurekaNodesUpdateIntervalMs)
    1115                 .append("peerEurekaStatusRefreshTimeIntervalMs",
    1116                         this.peerEurekaStatusRefreshTimeIntervalMs)
    1117                 .append("peerNodeConnectTimeoutMs", this.peerNodeConnectTimeoutMs)
    1118                 .append("peerNodeConnectionIdleTimeoutSeconds",
    1119                         this.peerNodeConnectionIdleTimeoutSeconds)
    1120                 .append("peerNodeReadTimeoutMs", this.peerNodeReadTimeoutMs)
    1121                 .append("peerNodeTotalConnections", this.peerNodeTotalConnections)
    1122                 .append("peerNodeTotalConnectionsPerHost",
    1123                         this.peerNodeTotalConnectionsPerHost)
    1124                 .append("primeAwsReplicaConnections", this.primeAwsReplicaConnections)
    1125                 .append("propertyResolver", this.propertyResolver)
    1126                 .append("rateLimiterBurstSize", this.rateLimiterBurstSize)
    1127                 .append("rateLimiterEnabled", this.rateLimiterEnabled)
    1128                 .append("rateLimiterFullFetchAverageRate",
    1129                         this.rateLimiterFullFetchAverageRate)
    1130                 .append("rateLimiterPrivilegedClients", this.rateLimiterPrivilegedClients)
    1131                 .append("rateLimiterRegistryFetchAverageRate",
    1132                         this.rateLimiterRegistryFetchAverageRate)
    1133                 .append("rateLimiterThrottleStandardClients",
    1134                         this.rateLimiterThrottleStandardClients)
    1135                 .append("registrySyncRetries", this.registrySyncRetries)
    1136                 .append("registrySyncRetryWaitMs", this.registrySyncRetryWaitMs)
    1137                 .append("remoteRegionAppWhitelist", this.remoteRegionAppWhitelist)
    1138                 .append("remoteRegionConnectTimeoutMs", this.remoteRegionConnectTimeoutMs)
    1139                 .append("remoteRegionConnectionIdleTimeoutSeconds",
    1140                         this.remoteRegionConnectionIdleTimeoutSeconds)
    1141                 .append("remoteRegionFetchThreadPoolSize",
    1142                         this.remoteRegionFetchThreadPoolSize)
    1143                 .append("remoteRegionReadTimeoutMs", this.remoteRegionReadTimeoutMs)
    1144                 .append("remoteRegionRegistryFetchInterval",
    1145                         this.remoteRegionRegistryFetchInterval)
    1146                 .append("remoteRegionTotalConnections", this.remoteRegionTotalConnections)
    1147                 .append("remoteRegionTotalConnectionsPerHost",
    1148                         this.remoteRegionTotalConnectionsPerHost)
    1149                 .append("remoteRegionTrustStore", this.remoteRegionTrustStore)
    1150                 .append("remoteRegionTrustStorePassword",
    1151                         this.remoteRegionTrustStorePassword)
    1152                 .append("remoteRegionUrls", this.remoteRegionUrls)
    1153                 .append("remoteRegionUrlsWithName", this.remoteRegionUrlsWithName)
    1154                 .append("renewalPercentThreshold", this.renewalPercentThreshold)
    1155                 .append("renewalThresholdUpdateIntervalMs",
    1156                         this.renewalThresholdUpdateIntervalMs)
    1157                 .append("responseCacheAutoExpirationInSeconds",
    1158                         this.responseCacheAutoExpirationInSeconds)
    1159                 .append("responseCacheUpdateIntervalMs",
    1160                         this.responseCacheUpdateIntervalMs)
    1161                 .append("retentionTimeInMSInDeltaQueue",
    1162                         this.retentionTimeInMSInDeltaQueue)
    1163                 .append("route53BindRebindRetries", this.route53BindRebindRetries)
    1164                 .append("route53BindingRetryIntervalMs",
    1165                         this.route53BindingRetryIntervalMs)
    1166                 .append("route53DomainTTL", this.route53DomainTTL)
    1167                 .append("syncWhenTimestampDiffers", this.syncWhenTimestampDiffers)
    1168                 .append("useReadOnlyResponseCache", this.useReadOnlyResponseCache)
    1169                 .append("waitTimeInMsWhenSyncEmpty", this.waitTimeInMsWhenSyncEmpty)
    1170                 .append("xmlCodecName", this.xmlCodecName)
    1171                 .append("initialCapacityOfResponseCache",
    1172                         this.initialCapacityOfResponseCache)
    1173                 .append("expectedClientRenewalIntervalSeconds",
    1174                         this.expectedClientRenewalIntervalSeconds)
    1175                 .append("useAwsAsgApi", this.useAwsAsgApi).append("myUrl", this.myUrl)
    1176                 .toString();
    1177     }
    1178 
    1179 }
    View Code

    ③ Eureka Server 初始化

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

     1 public void start() {
     2     new Thread(() -> {
     3         try {
     4             // TODO: is this class even needed now?
     5             eurekaServerBootstrap.contextInitialized(
     6                     EurekaServerInitializerConfiguration.this.servletContext);
     7             log.info("Started Eureka Server");
     8 
     9             publish(new EurekaRegistryAvailableEvent(getEurekaServerConfig()));
    10             EurekaServerInitializerConfiguration.this.running = true;
    11             publish(new EurekaServerStartedEvent(getEurekaServerConfig()));
    12         }
    13         catch (Exception ex) {
    14             // Help!
    15             log.error("Could not initialize Eureka servlet context", ex);
    16         }
    17     }).start();
    18 }
    View Code

    2、spring-cloud-starter-netflix-eureka-client

    ① Eureka Client 自动化配置

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

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

      1 @Configuration(proxyBeanMethods = false)
      2 @EnableConfigurationProperties
      3 @ConditionalOnClass(EurekaClientConfig.class)
      4 @ConditionalOnProperty(value = "eureka.client.enabled", matchIfMissing = true)
      5 @ConditionalOnDiscoveryEnabled
      6 @AutoConfigureBefore({ NoopDiscoveryClientAutoConfiguration.class,
      7         CommonsClientAutoConfiguration.class, ServiceRegistryAutoConfiguration.class })
      8 @AutoConfigureAfter(name = {
      9         "org.springframework.cloud.netflix.eureka.config.DiscoveryClientOptionalArgsConfiguration",
     10         "org.springframework.cloud.autoconfigure.RefreshAutoConfiguration",
     11         "org.springframework.cloud.netflix.eureka.EurekaDiscoveryClientConfiguration",
     12         "org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationAutoConfiguration" })
     13 public class EurekaClientAutoConfiguration {
     14 
     15     private ConfigurableEnvironment env;
     16 
     17     public EurekaClientAutoConfiguration(ConfigurableEnvironment env) {
     18         this.env = env;
     19     }
     20 
     21     @Bean
     22     public HasFeatures eurekaFeature() {
     23         return HasFeatures.namedFeature("Eureka Client", EurekaClient.class);
     24     }
     25 
     26     @Bean
     27     @ConditionalOnMissingBean(value = EurekaClientConfig.class,
     28             search = SearchStrategy.CURRENT)
     29     public EurekaClientConfigBean eurekaClientConfigBean(ConfigurableEnvironment env) {
     30         return new EurekaClientConfigBean();
     31     }
     32 
     33     @Bean
     34     @ConditionalOnMissingBean
     35     public ManagementMetadataProvider serviceManagementMetadataProvider() {
     36         return new DefaultManagementMetadataProvider();
     37     }
     38 
     39     private String getProperty(String property) {
     40         return this.env.containsProperty(property) ? this.env.getProperty(property) : "";
     41     }
     42 
     43     @Bean
     44     @ConditionalOnMissingBean(value = EurekaInstanceConfig.class,
     45             search = SearchStrategy.CURRENT)
     46     public EurekaInstanceConfigBean eurekaInstanceConfigBean(InetUtils inetUtils,
     47             ManagementMetadataProvider managementMetadataProvider) {
     48         String hostname = getProperty("eureka.instance.hostname");
     49         boolean preferIpAddress = Boolean
     50                 .parseBoolean(getProperty("eureka.instance.prefer-ip-address"));
     51         String ipAddress = getProperty("eureka.instance.ip-address");
     52         boolean isSecurePortEnabled = Boolean
     53                 .parseBoolean(getProperty("eureka.instance.secure-port-enabled"));
     54 
     55         String serverContextPath = env.getProperty("server.servlet.context-path", "/");
     56         int serverPort = Integer.parseInt(
     57                 env.getProperty("server.port", env.getProperty("port", "8080")));
     58 
     59         Integer managementPort = env.getProperty("management.server.port", Integer.class);
     60         String managementContextPath = env
     61                 .getProperty("management.server.servlet.context-path");
     62         Integer jmxPort = env.getProperty("com.sun.management.jmxremote.port",
     63                 Integer.class);
     64         EurekaInstanceConfigBean instance = new EurekaInstanceConfigBean(inetUtils);
     65 
     66         instance.setNonSecurePort(serverPort);
     67         instance.setInstanceId(getDefaultInstanceId(env));
     68         instance.setPreferIpAddress(preferIpAddress);
     69         instance.setSecurePortEnabled(isSecurePortEnabled);
     70         if (StringUtils.hasText(ipAddress)) {
     71             instance.setIpAddress(ipAddress);
     72         }
     73 
     74         if (isSecurePortEnabled) {
     75             instance.setSecurePort(serverPort);
     76         }
     77 
     78         if (StringUtils.hasText(hostname)) {
     79             instance.setHostname(hostname);
     80         }
     81         String statusPageUrlPath = getProperty("eureka.instance.status-page-url-path");
     82         String healthCheckUrlPath = getProperty("eureka.instance.health-check-url-path");
     83 
     84         if (StringUtils.hasText(statusPageUrlPath)) {
     85             instance.setStatusPageUrlPath(statusPageUrlPath);
     86         }
     87         if (StringUtils.hasText(healthCheckUrlPath)) {
     88             instance.setHealthCheckUrlPath(healthCheckUrlPath);
     89         }
     90 
     91         ManagementMetadata metadata = managementMetadataProvider.get(instance, serverPort,
     92                 serverContextPath, managementContextPath, managementPort);
     93 
     94         if (metadata != null) {
     95             instance.setStatusPageUrl(metadata.getStatusPageUrl());
     96             instance.setHealthCheckUrl(metadata.getHealthCheckUrl());
     97             if (instance.isSecurePortEnabled()) {
     98                 instance.setSecureHealthCheckUrl(metadata.getSecureHealthCheckUrl());
     99             }
    100             Map<String, String> metadataMap = instance.getMetadataMap();
    101             metadataMap.computeIfAbsent("management.port",
    102                     k -> String.valueOf(metadata.getManagementPort()));
    103         }
    104         else {
    105             // without the metadata the status and health check URLs will not be set
    106             // and the status page and health check url paths will not include the
    107             // context path so set them here
    108             if (StringUtils.hasText(managementContextPath)) {
    109                 instance.setHealthCheckUrlPath(
    110                         managementContextPath + instance.getHealthCheckUrlPath());
    111                 instance.setStatusPageUrlPath(
    112                         managementContextPath + instance.getStatusPageUrlPath());
    113             }
    114         }
    115 
    116         setupJmxPort(instance, jmxPort);
    117         return instance;
    118     }
    119 
    120     private void setupJmxPort(EurekaInstanceConfigBean instance, Integer jmxPort) {
    121         Map<String, String> metadataMap = instance.getMetadataMap();
    122         if (metadataMap.get("jmx.port") == null && jmxPort != null) {
    123             metadataMap.put("jmx.port", String.valueOf(jmxPort));
    124         }
    125     }
    126 
    127     @Bean
    128     public EurekaServiceRegistry eurekaServiceRegistry() {
    129         return new EurekaServiceRegistry();
    130     }
    131 
    132     // @Bean
    133     // @ConditionalOnBean(AutoServiceRegistrationProperties.class)
    134     // @ConditionalOnProperty(value =
    135     // "spring.cloud.service-registry.auto-registration.enabled", matchIfMissing = true)
    136     // public EurekaRegistration eurekaRegistration(EurekaClient eurekaClient,
    137     // CloudEurekaInstanceConfig instanceConfig, ApplicationInfoManager
    138     // applicationInfoManager, ObjectProvider<HealthCheckHandler> healthCheckHandler) {
    139     // return EurekaRegistration.builder(instanceConfig)
    140     // .with(applicationInfoManager)
    141     // .with(eurekaClient)
    142     // .with(healthCheckHandler)
    143     // .build();
    144     // }
    145 
    146     @Bean
    147     @ConditionalOnBean(AutoServiceRegistrationProperties.class)
    148     @ConditionalOnProperty(
    149             value = "spring.cloud.service-registry.auto-registration.enabled",
    150             matchIfMissing = true)
    151     public EurekaAutoServiceRegistration eurekaAutoServiceRegistration(
    152             ApplicationContext context, EurekaServiceRegistry registry,
    153             EurekaRegistration registration) {
    154         return new EurekaAutoServiceRegistration(context, registry, registration);
    155     }
    156 
    157     @Configuration(proxyBeanMethods = false)
    158     @ConditionalOnMissingRefreshScope
    159     protected static class EurekaClientConfiguration {
    160 
    161         @Autowired
    162         private ApplicationContext context;
    163 
    164         @Autowired
    165         private AbstractDiscoveryClientOptionalArgs<?> optionalArgs;
    166 
    167         @Bean(destroyMethod = "shutdown")
    168         @ConditionalOnMissingBean(value = EurekaClient.class,
    169                 search = SearchStrategy.CURRENT)
    170         public EurekaClient eurekaClient(ApplicationInfoManager manager,
    171                 EurekaClientConfig config) {
    172             return new CloudEurekaClient(manager, config, this.optionalArgs,
    173                     this.context);
    174         }
    175 
    176         @Bean
    177         @ConditionalOnMissingBean(value = ApplicationInfoManager.class,
    178                 search = SearchStrategy.CURRENT)
    179         public ApplicationInfoManager eurekaApplicationInfoManager(
    180                 EurekaInstanceConfig config) {
    181             InstanceInfo instanceInfo = new InstanceInfoFactory().create(config);
    182             return new ApplicationInfoManager(config, instanceInfo);
    183         }
    184 
    185         @Bean
    186         @ConditionalOnBean(AutoServiceRegistrationProperties.class)
    187         @ConditionalOnProperty(
    188                 value = "spring.cloud.service-registry.auto-registration.enabled",
    189                 matchIfMissing = true)
    190         public EurekaRegistration eurekaRegistration(EurekaClient eurekaClient,
    191                 CloudEurekaInstanceConfig instanceConfig,
    192                 ApplicationInfoManager applicationInfoManager, @Autowired(
    193                         required = false) ObjectProvider<HealthCheckHandler> healthCheckHandler) {
    194             return EurekaRegistration.builder(instanceConfig).with(applicationInfoManager)
    195                     .with(eurekaClient).with(healthCheckHandler).build();
    196         }
    197 
    198     }
    199 
    200     @Configuration(proxyBeanMethods = false)
    201     @ConditionalOnRefreshScope
    202     protected static class RefreshableEurekaClientConfiguration {
    203 
    204         @Autowired
    205         private ApplicationContext context;
    206 
    207         @Autowired
    208         private AbstractDiscoveryClientOptionalArgs<?> optionalArgs;
    209 
    210         @Bean(destroyMethod = "shutdown")
    211         @ConditionalOnMissingBean(value = EurekaClient.class,
    212                 search = SearchStrategy.CURRENT)
    213         @org.springframework.cloud.context.config.annotation.RefreshScope
    214         @Lazy
    215         public EurekaClient eurekaClient(ApplicationInfoManager manager,
    216                 EurekaClientConfig config, EurekaInstanceConfig instance,
    217                 @Autowired(required = false) HealthCheckHandler healthCheckHandler) {
    218             // If we use the proxy of the ApplicationInfoManager we could run into a
    219             // problem
    220             // when shutdown is called on the CloudEurekaClient where the
    221             // ApplicationInfoManager bean is
    222             // requested but wont be allowed because we are shutting down. To avoid this
    223             // we use the
    224             // object directly.
    225             ApplicationInfoManager appManager;
    226             if (AopUtils.isAopProxy(manager)) {
    227                 appManager = ProxyUtils.getTargetObject(manager);
    228             }
    229             else {
    230                 appManager = manager;
    231             }
    232             CloudEurekaClient cloudEurekaClient = new CloudEurekaClient(appManager,
    233                     config, this.optionalArgs, this.context);
    234             cloudEurekaClient.registerHealthCheck(healthCheckHandler);
    235             return cloudEurekaClient;
    236         }
    237 
    238         @Bean
    239         @ConditionalOnMissingBean(value = ApplicationInfoManager.class,
    240                 search = SearchStrategy.CURRENT)
    241         @org.springframework.cloud.context.config.annotation.RefreshScope
    242         @Lazy
    243         public ApplicationInfoManager eurekaApplicationInfoManager(
    244                 EurekaInstanceConfig config) {
    245             InstanceInfo instanceInfo = new InstanceInfoFactory().create(config);
    246             return new ApplicationInfoManager(config, instanceInfo);
    247         }
    248 
    249         @Bean
    250         @org.springframework.cloud.context.config.annotation.RefreshScope
    251         @ConditionalOnBean(AutoServiceRegistrationProperties.class)
    252         @ConditionalOnProperty(
    253                 value = "spring.cloud.service-registry.auto-registration.enabled",
    254                 matchIfMissing = true)
    255         public EurekaRegistration eurekaRegistration(EurekaClient eurekaClient,
    256                 CloudEurekaInstanceConfig instanceConfig,
    257                 ApplicationInfoManager applicationInfoManager, @Autowired(
    258                         required = false) ObjectProvider<HealthCheckHandler> healthCheckHandler) {
    259             return EurekaRegistration.builder(instanceConfig).with(applicationInfoManager)
    260                     .with(eurekaClient).with(healthCheckHandler).build();
    261         }
    262 
    263     }
    264 
    265     @Target({ ElementType.TYPE, ElementType.METHOD })
    266     @Retention(RetentionPolicy.RUNTIME)
    267     @Documented
    268     @Conditional(OnMissingRefreshScopeCondition.class)
    269     @interface ConditionalOnMissingRefreshScope {
    270 
    271     }
    272 
    273     @Target({ ElementType.TYPE, ElementType.METHOD })
    274     @Retention(RetentionPolicy.RUNTIME)
    275     @Documented
    276     @ConditionalOnClass(RefreshScope.class)
    277     @ConditionalOnBean(RefreshAutoConfiguration.class)
    278     @ConditionalOnProperty(value = "eureka.client.refresh.enable", havingValue = "true",
    279             matchIfMissing = true)
    280     @interface ConditionalOnRefreshScope {
    281 
    282     }
    283 
    284     private static class OnMissingRefreshScopeCondition extends AnyNestedCondition {
    285 
    286         OnMissingRefreshScopeCondition() {
    287             super(ConfigurationPhase.REGISTER_BEAN);
    288         }
    289 
    290         @ConditionalOnMissingClass("org.springframework.cloud.context.scope.refresh.RefreshScope")
    291         static class MissingClass {
    292 
    293         }
    294 
    295         @ConditionalOnMissingBean(RefreshAutoConfiguration.class)
    296         static class MissingScope {
    297 
    298         }
    299 
    300         @ConditionalOnProperty(value = "eureka.client.refresh.enable",
    301                 havingValue = "false")
    302         static class OnPropertyDisabled {
    303 
    304         }
    305 
    306     }
    307 
    308     @Configuration(proxyBeanMethods = false)
    309     @ConditionalOnClass(Health.class)
    310     protected static class EurekaHealthIndicatorConfiguration {
    311 
    312         @Bean
    313         @ConditionalOnMissingBean
    314         @ConditionalOnEnabledHealthIndicator("eureka")
    315         public EurekaHealthIndicator eurekaHealthIndicator(EurekaClient eurekaClient,
    316                 EurekaInstanceConfig instanceConfig, EurekaClientConfig clientConfig) {
    317             return new EurekaHealthIndicator(eurekaClient, instanceConfig, clientConfig);
    318         }
    319 
    320     }
    321 
    322 }
    View Code

    ② Eureka Client 注册

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

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

     1 public void start() {
     2     // only set the port if the nonSecurePort or securePort is 0 and this.port != 0
     3     if (this.port.get() != 0) {
     4         if (this.registration.getNonSecurePort() == 0) {
     5             this.registration.setNonSecurePort(this.port.get());
     6         }
     7 
     8         if (this.registration.getSecurePort() == 0 && this.registration.isSecure()) {
     9             this.registration.setSecurePort(this.port.get());
    10         }
    11     }
    12 
    13     // only initialize if nonSecurePort is greater than 0 and it isn't already running
    14     // because of containerPortInitializer below
    15     if (!this.running.get() && this.registration.getNonSecurePort() > 0) {
    16 
    17         this.serviceRegistry.register(this.registration);
    18 
    19         this.context.publishEvent(new InstanceRegisteredEvent<>(this,
    20                 this.registration.getInstanceConfig()));
    21         this.running.set(true);
    22     }
    23 }
    View Code

    十四、Eureka 总结

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

    1、Eureka Server 提供的 API

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

    2、Eureka Client 核心参数

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

    ① 基本参数

    ② 定时任务参数

    ③ http 参数

    3、Eureka Server 核心参数

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

    ① 基本参数

    ② 多级缓存参数

    ③ 集群参数

    ④ http 参数

    4、Eureka 核心功能

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

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

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

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

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

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

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

    5、Eureka 核心类和组件

    ① 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

    注册表类结构:

    ② 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

    ③ 集群同步:

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

    6、Eureka 后台任务

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

    ① Eureka Client

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

    ② 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 的单项任务发送到集群节点

    7、Eureka 的一些优秀设计

    ① Eureka 注册中心

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

    ② 基于接口的配置读取方式

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

    ③ 定时任务监管器 TimedSupervisorTask

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

    ④ 最近一分钟计数器 MeasuredRate

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

    ⑤ 定时任务补偿时间

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

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

    ⑥ 并发队列的应用

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

    ⑦ 三级缓存高性能读取

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

    ⑧ 增量数据更新

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

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

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

    ⑩ 自我保护机制

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

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

    ⑪ 监控统计

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

    ⑫ Eureka Server 集群

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

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

    8、Eureka Server 承载高并发访问压力

    ① 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 集群部署的实例数。

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

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

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

    9、Eureka 整体架构

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

  • 相关阅读:
    Win32K里的死循环
    Oracle 经常使用命令小结
    ImageView 会盖掉ScrollView的OnTouchListener,监听Activity的手势
    怎样安装pip--python的包管理工具
    设计模式-模板方法
    django中怎样生成非HTML格式的内容。
    SharePoint 2013 设置自己定义布局页
    Android读写JSON格式的数据之JsonWriter和JsonReader
    基于Linux 3.0.8 Samsung FIMC(S5PV210) 的摄像头驱动框架解读(一)
    BZOJ-1923-外星千足虫-SDOI2010
  • 原文地址:https://www.cnblogs.com/chiangchou/p/eureka-3.html
Copyright © 2020-2023  润新知