我是靠谱客的博主 哭泣萝莉,这篇文章主要介绍eureka--踢出实例原理,现在分享给大家,希望可以做个参考。

1. 为什么要踢出注册实例

一个实例注册到eureka-server中。如果在规定的时间内没法发送心跳(续租)信息。服务器有权把它赶出。这类比于你租房,如果在规定的时间内你没有交房租,那么房东有权把你赶出。

但是:在网络世界踢出实例比现实世界有点复杂:原因是网络分区

复制代码
1
2
网络分区:检测网络失败是很困难的,我们得到其他节点状态的信息就是通过网络来得到,延迟跟网络失败也无从区分。

2.eureka 如何解决网络分区

  1. eukeka server默认开启了自我保护机制,当然可以关闭
  2. 如果开启自我保护机制。则eureka踢出实例时有一个最大的踢出个数(不能把所有的实例都踢出,剩下的要保护起来,不能在踢出了)。

    复制代码
    1
    2
    3
    4
    int expiredLeasesSize= 过期实例数; int registrySizeThreshold = (int) (registrySize * serverConfig.getRenewalPercentThreshold());//需要保护的最小实例数 int toEvict = Math.min(expiredLeases.size(), evictionLimit);//server端决定本次踢出的实例数
  3. server端踢出时有一定的技巧,那就是随机的踢出toEvit大小的实例。如果不随机踢出,那么会造成整个应用下线。不可提供服务。这样影响应该均匀的分布到所有的应用中。

3. eukeak 踢出源码实现

3.1 eureka 通过一个Timer定时器来定时的踢出过期的实例

复制代码
1
2
3
4
5
6
7
8
9
10
private Timer evictionTimer = new Timer("Eureka-EvictionTimer", true); //定时任务的初始化方法 protected void postInit() { //...其他代码 evictionTaskRef.set(new EvictionTask()); evictionTimer.schedule(evictionTaskRef.get(), serverConfig.getEvictionIntervalTimerInMs(), serverConfig.getEvictionIntervalTimerInMs()); }

一个定时器,必须有个任务。我们首先看下踢出过期实例的任务:EvictionTask

2.1.1 EvictionTask源码分析

重点看下。补偿时间的实现。
为什么定时任务执行时需要注意补偿时间?
假如一个定时任务在10:00开始,每隔1秒执行一次任务,但是由于full gc (Stop the word) 或者别的原因,造成下次任务实际执行的时间是:10:02。因此任务执行有延迟。整整延迟了1秒,这l秒就是上面提到的补偿时间。

补偿时间计算算法实现:

  1. 一个变量存放上次定时任务执行的具体时间点,如:
    lastExecutionNanosRef

  2. 计算从上次任务执行以来到本地任务执行的时间差值:

    复制代码
    1
    2
    long elapsedMs = TimeUnit.NANOSECONDS.toMillis(currNanos - lastNanos);
  3. 计算补偿时间:两次任务的时间间隔差- 定时任务的时间间隔差

    复制代码
    1
    2
    long compensationTime = elapsedMs - serverConfig.getEvictionIntervalTimerInMs();
  4. compensationTime<=0 ? 0:compensationTime

    源码:

    复制代码
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    /** * 长时间没有续租时,进行回收的定时任务 * * eviction:回收 */ /* visible for testing */ class EvictionTask extends TimerTask { private final AtomicLong lastExecutionNanosRef = new AtomicLong(0l); @Override public void run() { try { long compensationTimeMs = getCompensationTimeMs(); logger.info("Running the evict task with compensationTime {}ms", compensationTimeMs); evict(compensationTimeMs); } catch (Throwable e) { logger.error("Could not run the evict task", e); } } /** * compute a compensation time defined as the actual time this task was executed since the prev iteration, * vs the configured amount of time for execution. This is useful for cases where changes in time (due to * clock skew or gc for example) causes the actual eviction task to execute later than the desired time * according to the configured cycle. * * 计算一个补偿时间作为下一个任务实际执行的时间。为什么这么做:是由于gc或者时钟便宜造成的时间改变。 * 造成时间任务执行的时间比期望的时间晚点。 * * * */ long getCompensationTimeMs() { /**当前时间的纳秒*/ long currNanos = getCurrentTimeNano(); /**以原子方式设置新值。并返回旧值*/ long lastNanos = lastExecutionNanosRef.getAndSet(currNanos); if (lastNanos == 0l) { return 0l; } /** * elapsed 表示过去的时间 * 自从上个定时任务执行以来。已经过了这么长时间 */ long elapsedMs = TimeUnit.NANOSECONDS.toMillis(currNanos - lastNanos); /** * 补偿时间 == 过去的时间 - 定时回收的时间间隔 * * < 0 ;代表时间还没到 * > = 0;表示时间已经过了。需要补偿这部分时间差值 */ long compensationTime = elapsedMs - serverConfig.getEvictionIntervalTimerInMs(); return compensationTime <= 0l ? 0l : compensationTime; } long getCurrentTimeNano() { // for testing return System.nanoTime(); } }
2.1.2 踢出源码分析
  1. 要解决的问题:网络分区
  2. 自我保护机制
  3. full gc等引起的补偿时间
  4. 洗牌算法 shuffle 参考:
    https://blog.csdn.net/ai_xiangjuan/article/details/80210899
复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
/** * 踢出过期实例 * @param additionalLeaseMs */ public void evict(long additionalLeaseMs) { logger.debug("Running the evict task"); /** * 是否支持续租过期 */ if (!isLeaseExpirationEnabled()) { logger.debug("DS: lease expiration is currently disabled."); return; } // We collect first all expired items, to evict them in random order. For large eviction sets, // if we do not that, we might wipe out whole apps before self preservation kicks in. By randomizing it, // the impact should be evenly distributed across all applications. /** * 收集所有的过期项到一个集合中,然后随机的踢出它们。 * 对于一个较大的踢出集合项,如果我们不这么做(随机的踢出),在进入自我保护之前,我们可能移除某个app的整个实例 * 这样影响应该均匀的分布到所有的应用程序中 */ List<Lease<InstanceInfo>> expiredLeases = new ArrayList<>(); for (Entry<String, Map<String, Lease<InstanceInfo>>> groupEntry : registry.entrySet()) { Map<String, Lease<InstanceInfo>> leaseMap = groupEntry.getValue(); if (leaseMap != null) { for (Entry<String, Lease<InstanceInfo>> leaseEntry : leaseMap.entrySet()) { Lease<InstanceInfo> lease = leaseEntry.getValue(); /*** * 判断实例过期;用到了补偿时间 */ if (lease.isExpired(additionalLeaseMs) && lease.getHolder() != null) { /**添加到过期实例的集合中*/ expiredLeases.add(lease); } } } } // To compensate for GC pauses or drifting local time, we need to use current registry size as a base for // triggering self-preservation. Without that we would wipe out full registry. /**本地注册表大小*/ int registrySize = (int) getLocalRegistrySize(); /**本地注册表的最大值。即阈值*/ /**最小续订百分比。 * 如果续租小于这个阀值。则过期会被禁用如果开启自我保护时。*/ int registrySizeThreshold = (int) (registrySize * serverConfig.getRenewalPercentThreshold()); /** * 踢出的最大限制 */ int evictionLimit = registrySize - registrySizeThreshold; /** * 决定踢出的实例数 */ int toEvict = Math.min(expiredLeases.size(), evictionLimit); if (toEvict > 0) { logger.info("Evicting {} items (expired={}, evictionLimit={})", toEvict, expiredLeases.size(), evictionLimit); /** * 创建一个随机数 */ Random random = new Random(System.currentTimeMillis()); for (int i = 0; i < toEvict; i++) { // Pick a random item (Knuth shuffle algorithm) //Knuth shuffle algorithm 参考:https://blog.csdn.net/ai_xiangjuan/article/details/80210899 //随机的挑选一个 int next = i + random.nextInt(expiredLeases.size() - i); //跟尾部的数组进行交换 Collections.swap(expiredLeases, i, next); Lease<InstanceInfo> lease = expiredLeases.get(i); String appName = lease.getHolder().getAppName(); String id = lease.getHolder().getId(); EXPIRED.increment(); logger.warn("DS: Registry: expired lease for {}/{}", appName, id); internalCancel(appName, id, false); } } }

最后

以上就是哭泣萝莉最近收集整理的关于eureka--踢出实例原理的全部内容,更多相关eureka--踢出实例原理内容请搜索靠谱客的其他文章。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(66)

评论列表共有 0 条评论

立即
投稿
返回
顶部