概述
1. 为什么要踢出注册实例
一个实例注册到eureka-server中。如果在规定的时间内没法发送心跳(续租)信息。服务器有权把它赶出。这类比于你租房,如果在规定的时间内你没有交房租,那么房东有权把你赶出。
但是:在网络世界踢出实例比现实世界有点复杂:原因是网络分区
网络分区:检测网络失败是很困难的,我们得到其他节点状态的信息就是通过网络来得到,延迟跟网络失败也无从区分。
2.eureka 如何解决网络分区
- eukeka server默认开启了自我保护机制,当然可以关闭
如果开启自我保护机制。则eureka踢出实例时有一个最大的踢出个数(不能把所有的实例都踢出,剩下的要保护起来,不能在踢出了)。
int expiredLeasesSize= 过期实例数; int registrySizeThreshold = (int) (registrySize * serverConfig.getRenewalPercentThreshold());//需要保护的最小实例数 int toEvict = Math.min(expiredLeases.size(), evictionLimit);//server端决定本次踢出的实例数
server端踢出时有一定的技巧,那就是随机的踢出toEvit大小的实例。如果不随机踢出,那么会造成整个应用下线。不可提供服务。这样影响应该均匀的分布到所有的应用中。
3. eukeak 踢出源码实现
3.1 eureka 通过一个Timer定时器来定时的踢出过期的实例
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秒就是上面提到的补偿时间。
补偿时间计算算法实现:
一个变量存放上次定时任务执行的具体时间点,如:
lastExecutionNanosRef计算从上次任务执行以来到本地任务执行的时间差值:
long elapsedMs = TimeUnit.NANOSECONDS.toMillis(currNanos - lastNanos);
计算补偿时间:两次任务的时间间隔差- 定时任务的时间间隔差
long compensationTime = elapsedMs - serverConfig.getEvictionIntervalTimerInMs();
compensationTime<=0 ? 0:compensationTime
源码:
/** * 长时间没有续租时,进行回收的定时任务 * * 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 踢出源码分析
- 要解决的问题:网络分区
- 自我保护机制
- full gc等引起的补偿时间
- 洗牌算法 shuffle 参考:
https://blog.csdn.net/ai_xiangjuan/article/details/80210899
/**
* 踢出过期实例
* @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--踢出实例原理所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复