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 }
先看它的 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 }
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.<zone></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 }
但是要注意,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 }
③ 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 }
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 }
② 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 }
十四、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 的整体架构、运行流程以及核心机制。