Eureka原理

58 篇文章 2 订阅
51 篇文章 0 订阅

Eureka是spring cloud中的一个负责服务注册与发现的组件。遵循着CAP理论中的A(可用性)P(分区容错性)。
在这里插入图片描述

一、Eureka 基本概念

Register - 服务注册

当 Eureka Client 向 Eureka Server 注册时,Eureka Client 提供自身的元数据,比如 IP 地址、端口、运行状况指标的 Url、主页地址等信息。

Renew - 服务续约

Eureka Client 在默认情况下会每隔 30 秒来发送一次心跳来进行服务续约。通过服务续约来告知 Eureka Server 该 Eureka Client 仍然可用,没有出现故障。

如果 Eureka Server 90 秒内没有收到 Eureka Client 的心跳,Eureka Server 会将该 Eureka Client 实例从注册列表中剔除。

注意:官网不建议修改服务续约的间隔时间。

Fetch Registries - 获取服务注册列表信息 

Eureka Client 从 Eureka Server 获取服务注册表信息,并将其缓存在本地(内存中)。Eureka Client 再从本地的注册列表中获取需要的服务信息,从而进行远程调用。

这个注册列表信息定时(每 30 秒)更新一次,每次返回的列表信息可能与当前缓存的数据不一致,Eureka Client 会自己处理这些信息。

Eureka Client 和 Eureka Server 可以使用 JSON 和 XML 数据格式进行通信,默认情况下,Eureka Client 使用 JSON 的方式来获取服务注册列表信息。

Eureka Client 缓存了所有的服务注册列表信息,其实这是一个 Eureka 的缺点,比较浪费内存,当服务器实例达到一定数量之后,比如 40K 以上的集群规模,每次更新内存都会消耗很大的资源,而且每个客户端实例都会缓存这么一份数据,实际上很多都是无效数据。

Cancel - 服务下线 

Eureka Client 在程序关闭时可以向 Eureka Server 发送下线请求。发送请求后,该客户端的实例信息将从 Eureka Server 的服务注册列表中删除。

该下线请求不会自动完成,需要在程序关闭时调用以下代码:

DiscoveryManager.getInstance().shutdownComponent();

注意这是一个过期方法,一般不会使用。

Eviction - 服务剔除 

默认情况下,如果 Eureka Client 连续 90 秒没有向 Eureka Server 发送服务续约(心跳),Eureka Server 会将该客户端的实例信息将从服务注册列表中删除,即服务剔除。 

二、客户端-服务端调用链路图 

三、 Eureka 注册表

注册表存储各个服务注册的相关信息,定义在 AbstractInstanceRegistry 类中,具体的代码如下:

public abstract class AbstractInstanceRegistry implements InstanceRegistry {
    private static final Logger logger = LoggerFactory.getLogger(AbstractInstanceRegistry.class);

    private static final String[] EMPTY_STR_ARRAY = new String[0];
    private final ConcurrentHashMap<String, Map<String, Lease<InstanceInfo>>> registry
            = new ConcurrentHashMap<String, Map<String, Lease<InstanceInfo>>>();
    protected Map<String, RemoteRegionRegistry> regionNameVSRemoteRegistry = new HashMap<String, RemoteRegionRegistry>();
    protected final ConcurrentMap<String, InstanceStatus> overriddenInstanceStatusMap = CacheBuilder
            .newBuilder().initialCapacity(500)
            .expireAfterAccess(1, TimeUnit.HOURS)
            .<String, InstanceStatus>build().asMap();

 可以看到是一个 ConcurrentHashMap 的存储结构:

  • key 是服务名称,value 也是一个 Map;
  • value 的 Map 的 key 是服务实例的 ID

value 的 Map 里的 value 是 Lease 类,Lease 中存储了实例的注册时间、上线时间等信息,还有具体的实例信息,比如 IP、端口、健康检查的地址等信息,对应的是 InstanceInfo。

public class Lease<T> {
    enum Action {
        Register, Cancel, Renew
    };

    public static final int DEFAULT_DURATION_IN_SECS = 90;

    private T holder; //实例信息 InstanceInfo
    private long evictionTimestamp; //取消注册时间
    private long registrationTimestamp; //服务注册的时间
    private long serviceUpTimestamp; //服务上线的时间
    // Make it volatile so that the expiration task would see this quicker
    private volatile long lastUpdateTimestamp; //最后更新的时间
    private long duration; //租约的时长
public class InstanceInfo {
    private static final String VERSION_UNKNOWN = "unknown";

    public static class PortWrapper {
        private final boolean enabled;
        private final int port;

        @JsonCreator
        public PortWrapper(@JsonProperty("@enabled") boolean enabled, @JsonProperty("$") int port) {
            this.enabled = enabled;
            this.port = port;
        }

        public boolean isEnabled() {
            return enabled;
        }

        public int getPort() {
            return port;
        }
    }

    private static final Logger logger = LoggerFactory.getLogger(InstanceInfo.class);

    public static final int DEFAULT_PORT = 7001;
    public static final int DEFAULT_SECURE_PORT = 7002;
    public static final int DEFAULT_COUNTRY_ID = 1; // US

    // The (fixed) instanceId for this instanceInfo. This should be unique within the scope of the appName.
    private volatile String instanceId;

    private volatile String appName;
    @Auto
    private volatile String appGroupName;

    private volatile String ipAddr;

    private static final String SID_DEFAULT = "na";
    @Deprecated
    private volatile String sid = SID_DEFAULT;

    private volatile int port = DEFAULT_PORT;
    private volatile int securePort = DEFAULT_SECURE_PORT;

    @Auto
    private volatile String homePageUrl;
    @Auto
    private volatile String statusPageUrl;
    @Auto
    private volatile String healthCheckUrl;
    @Auto
    private volatile String secureHealthCheckUrl;
    @Auto
    private volatile String vipAddress;
    @Auto
    private volatile String secureVipAddress;

四、Eureka 高可用架构分析

Eureka 的架构主要分为 Eureka Server 和 Eureka Client 两部分Eureka Client 又分为 Applicaton Service 和 Application Client

  • Applicaton Service 就是服务提供者;
  • Application Client 就是服务消费者;

每个区域有一个 Eureka 集群,并且每个区域至少有一个 Eureka Server 可以处理区域故障,以防服务器瘫痪。集群模式是peer to peer,不存在选主模式。

五、Eureka 集群各节点的数据同步

1)服务启动时

从其他节点复制注册表信息,调用 EurekaServerBootstrap#initEurekaServerContext 

@Override
public void start() {
	new Thread(new Runnable() {
		@Override
		public void run() {
			try {
				//TODO: is this class even needed now?
				//第一步: 异步实例化上下文
eurekaServerBootstrap.contextInitialized(EurekaServerInitializerConfiguration.this.servletContext);
				log.info("Started Eureka Server");

				publish(new EurekaRegistryAvailableEvent(getEurekaServerConfig()));
				EurekaServerInitializerConfiguration.this.running = true;
				publish(new EurekaServerStartedEvent(getEurekaServerConfig()));
			}
			catch (Exception ex) {
				// Help!
				log.error("Could not initialize Eureka servlet context", ex);
			}
		}
	}).start();
}

public void contextInitialized(ServletContext context) {
	try {
		initEurekaEnvironment();

        // 第二步:实例化eureka上下文
		initEurekaServerContext();

		context.setAttribute(EurekaServerContext.class.getName(), this.serverContext);
	}
	catch (Throwable e) {
		log.error("Cannot bootstrap eureka server :", e);
		throw new RuntimeException("Cannot bootstrap eureka server :", e);
	}
}

protected void initEurekaServerContext() throws Exception {
	// For backward compatibility
	JsonXStream.getInstance().registerConverter(new V1AwareInstanceInfoConverter(),
			XStream.PRIORITY_VERY_HIGH);
	XmlXStream.getInstance().registerConverter(new V1AwareInstanceInfoConverter(),
			XStream.PRIORITY_VERY_HIGH);

	if (isAws(this.applicationInfoManager.getInfo())) {
		this.awsBinder = new AwsBinderDelegate(this.eurekaServerConfig,
				this.eurekaClientConfig, this.registry, this.applicationInfoManager);
		this.awsBinder.start();
	}

	EurekaServerContextHolder.initialize(this.serverContext);

	log.info("Initialized server context");

    // 第三部:从相邻的节点同步注册信息
	// Copy registry from neighboring eureka node
	int registryCount = this.registry.syncUp();
	this.registry.openForTraffic(this.applicationInfoManager, registryCount);

	// Register all monitoring statistics.
	EurekaMonitors.registerAllStats();
}

2) 节点更新注册表

 一个节点更新注册表信息时,同步到其他节点,即注册到A,同步到集群中的其他节点B、C中。
在进行注册、续约、下线等操作时,同步到其他eureka节点。
com.netflix.eureka.registry.PeerAwareInstanceRegistryImpl#replicateToPeers

public void register(final InstanceInfo info, final boolean isReplication) {
	int leaseDuration = Lease.DEFAULT_DURATION_IN_SECS;
	if (info.getLeaseInfo() != null && info.getLeaseInfo().getDurationInSecs() > 0) {
		leaseDuration = info.getLeaseInfo().getDurationInSecs();
	}
	super.register(info, leaseDuration, isReplication);
    // 注册同步到其他节点
	replicateToPeers(Action.Register, info.getAppName(), info.getId(), info, null, isReplication);
}


public boolean renew(final String appName, final String id, final boolean isReplication) {
	if (super.renew(appName, id, isReplication)) {
        // 续约同步到其他节点
		replicateToPeers(Action.Heartbeat, appName, id, null, null, isReplication);
		return true;
	}
	return false;
}
......其他动作也一样调用replicateToPeers

private void replicateToPeers(Action action, String appName, String id,
							  InstanceInfo info /* optional */,
							  InstanceStatus newStatus /* optional */, boolean isReplication) {
	Stopwatch tracer = action.getTimer().start();
	try {
		if (isReplication) {
			numberOfReplicationsLastMin.increment();
		}
		// If it is a replication already, do not replicate again as this will create a poison replication
		if (peerEurekaNodes == Collections.EMPTY_LIST || isReplication) {
			return;
		}

        // 循环同步到各个节点
		for (final PeerEurekaNode node : peerEurekaNodes.getPeerEurekaNodes()) {
			// If the url represents this host, do not replicate to yourself.
			if (peerEurekaNodes.isThisMyUrl(node.getServiceUrl())) {
				continue;
			}
			replicateInstanceActionsToPeers(action, appName, id, info, newStatus, node);
		}
	} finally {
		tracer.stop();
	}
}

通过 peerEurekaNodes.getPeerEurekaNodes() 得到 Eureka Server 的所有节点信息;
在当前节点中循环进行复制操作,需要排除自己,不需要将信息同步给自己;
复制操作会根据 Action 来进行对应的操作,通过 node 对象的方法构建复制的任务,任务本质还是通过调用 Eureka 的 Rest API 来进行操作的。

六、三级缓存

1)缓存介绍

一级缓存:实时更新,Eureka UI端请求是这里的服务注册信息

类:AbstractInstanceRegistry

private final ConcurrentHashMap<String, Map<String, Lease<InstanceInfo>>> registry
            = new ConcurrentHashMap<String, Map<String, Lease<InstanceInfo>>>();

二级缓存:实时更新,缓存时间180s

三级缓存:周期更新,默认是30s从readWriteCacheMap更新,Eureka Client默认从这里更新服务注册信息可配置直接从readWriteCacheMap更新

类:ResponseCacheImpl

// 三级缓存
private final ConcurrentMap<Key, Value> readOnlyCacheMap = new ConcurrentHashMap<Key, Value>();

// 二级缓存
private final LoadingCache<Key, Value> readWriteCacheMap;

2)多种过期策略

主动过期:当服务实例发生注册、下线、故障的时候,ReadWriteCacheMap中所有的缓存过期掉。
 

定时过期:readWriteCacheMap在构建的时候,指定了一个自动过期的时间,默认值就是180秒,所以你往readWriteCacheMap中放入一个数据,180秒过后,就将这个数据给他过期了。
 

被动过期:默认是每隔30秒,执行一个定时调度的线程任务,对readOnlyCacheMap和readWriteCacheMap中的数据进行一个比对;如果两块数据是不一致的,那么就将readWriteCacheMap中的数据放到readOnlyCacheMap中来;

总结:
1、register本地注册表 (一级缓存):是实时变化的。
2、readWriteCacheMap(二级缓存):是对一级缓存的缓存。一级缓存发生变化时,过期二级缓存中的该缓存,重写load方法;当二级缓存中不存在时,从一级缓存中同步。
3、readOnlyCacheMap只读续存:只从读写缓存里copy数据,两种情况:

  • 定时任务从读写缓存里同步;
  • Eureka Client获取注册信息时,打到读写缓存时同步,与读写缓存同步。(读缓存不存在,从读写缓存同步)

3)读写缓存初始化 

ResponseCacheImpl(EurekaServerConfig serverConfig, ServerCodecs serverCodecs, AbstractInstanceRegistry registry) {
	this.serverConfig = serverConfig;
	this.serverCodecs = serverCodecs;
	this.shouldUseReadOnlyResponseCache = serverConfig.shouldUseReadOnlyResponseCache();
	this.registry = registry;

	long responseCacheUpdateIntervalMs = serverConfig.getResponseCacheUpdateIntervalMs();

// Key 过期设定		
this.readWriteCacheMap =
			CacheBuilder.newBuilder().initialCapacity(serverConfig.getInitialCapacityOfResponseCache()).expireAfterWrite(serverConfig.getResponseCacheAutoExpirationInSeconds(), TimeUnit.SECONDS)
					.removalListener(new RemovalListener<Key, Value>() {
						@Override
						public void onRemoval(RemovalNotification<Key, Value> notification) {
							Key removedKey = notification.getKey();
							if (removedKey.hasRegions()) {
								Key cloneWithNoRegions = removedKey.cloneWithoutRegions();
								regionSpecificKeys.remove(cloneWithNoRegions, removedKey);
							}
						}
					})
					.build(new CacheLoader<Key, Value>() {
                        // 重新加载
						@Override
						public Value load(Key key) throws Exception {
							if (key.hasRegions()) {
								Key cloneWithNoRegions = key.cloneWithoutRegions();
								regionSpecificKeys.put(cloneWithNoRegions, key);
							}
							Value value = generatePayload(key);
							return value;
						}
					});

	if (shouldUseReadOnlyResponseCache) {
		timer.schedule(getCacheUpdateTask(),
				new Date(((System.currentTimeMillis() / responseCacheUpdateIntervalMs) * responseCacheUpdateIntervalMs)
						+ responseCacheUpdateIntervalMs),
				responseCacheUpdateIntervalMs);
	}

	try {
		Monitors.registerObject(this);
	} catch (Throwable e) {
		logger.warn("Cannot register the JMX monitor for the InstanceRegistry", e);
	}
}

readWriteCacheMap中key定时过期。
CacheLoader类load()方法,在get获取不到指定key值时,调用load方法重新从注册表中获取值存入读写缓存。

对与注册表来说,写操作是在注册等请求时读操作是在客户端差异获取注册表时,此时写操作必然已完成,从而实现读写分离。

4)注册一个服务实例

向注册表中写入服务实例信息,并使得二级缓存中的该实例失效。


public void register(InstanceInfo registrant, int leaseDuration, boolean isReplication) {
	try {
		read.lock();
        // 读取服务的实例信息
		Map<String, Lease<InstanceInfo>> gMap = registry.get(registrant.getAppName());
		REGISTER.increment(isReplication);
		if (gMap == null) {
			final ConcurrentHashMap<String, Lease<InstanceInfo>> gNewMap = new ConcurrentHashMap<String, Lease<InstanceInfo>>();
			gMap = registry.putIfAbsent(registrant.getAppName(), gNewMap);
			if (gMap == null) {
				gMap = gNewMap;
			}
		}
		Lease<InstanceInfo> existingLease = gMap.get(registrant.getId());
		// Retain the last dirty timestamp without overwriting it, if there is already a lease
		if (existingLease != null && (existingLease.getHolder() != null)) {
			Long existingLastDirtyTimestamp = existingLease.getHolder().getLastDirtyTimestamp();
			Long registrationLastDirtyTimestamp = registrant.getLastDirtyTimestamp();
			logger.debug("Existing lease found (existing={}, provided={}", existingLastDirtyTimestamp, registrationLastDirtyTimestamp);

			// this is a > instead of a >= because if the timestamps are equal, we still take the remote transmitted
			// InstanceInfo instead of the server local copy.
			if (existingLastDirtyTimestamp > registrationLastDirtyTimestamp) {
				logger.warn("There is an existing lease and the existing lease's dirty timestamp {} is greater" +
						" than the one that is being registered {}", existingLastDirtyTimestamp, registrationLastDirtyTimestamp);
				logger.warn("Using the existing instanceInfo instead of the new instanceInfo as the registrant");
				registrant = existingLease.getHolder();
			}
		} else {
			// The lease does not exist and hence it is a new registration
			synchronized (lock) {
				if (this.expectedNumberOfClientsSendingRenews > 0) {
					// Since the client wants to register it, increase the number of clients sending renews
					this.expectedNumberOfClientsSendingRenews = this.expectedNumberOfClientsSendingRenews + 1;
					updateRenewsPerMinThreshold();
				}
			}
			logger.debug("No previous lease information found; it is new registration");
		}
		Lease<InstanceInfo> lease = new Lease<InstanceInfo>(registrant, leaseDuration);
		if (existingLease != null) {
			lease.setServiceUpTimestamp(existingLease.getServiceUpTimestamp());
		}


        // 注册服务放到一级缓存
		gMap.put(registrant.getId(), lease);
		synchronized (recentRegisteredQueue) {
			recentRegisteredQueue.add(new Pair<Long, String>(
					System.currentTimeMillis(),
					registrant.getAppName() + "(" + registrant.getId() + ")"));
		}
		// This is where the initial state transfer of overridden status happens
		if (!InstanceStatus.UNKNOWN.equals(registrant.getOverriddenStatus())) {
			logger.debug("Found overridden status {} for instance {}. Checking to see if needs to be add to the "
							+ "overrides", registrant.getOverriddenStatus(), registrant.getId());
			if (!overriddenInstanceStatusMap.containsKey(registrant.getId())) {
				logger.info("Not found overridden id {} and hence adding it", registrant.getId());
				overriddenInstanceStatusMap.put(registrant.getId(), registrant.getOverriddenStatus());
			}
		}
		InstanceStatus overriddenStatusFromMap = overriddenInstanceStatusMap.get(registrant.getId());
		if (overriddenStatusFromMap != null) {
			logger.info("Storing overridden status {} from map", overriddenStatusFromMap);
			registrant.setOverriddenStatus(overriddenStatusFromMap);
		}

		// Set the status based on the overridden status rules
		InstanceStatus overriddenInstanceStatus = getOverriddenInstanceStatus(registrant, existingLease, isReplication);
		registrant.setStatusWithoutDirty(overriddenInstanceStatus);

		// If the lease is registered with UP status, set lease service up timestamp
		if (InstanceStatus.UP.equals(registrant.getStatus())) {
			lease.serviceUp();
		}
		registrant.setActionType(ActionType.ADDED);
		recentlyChangedQueue.add(new RecentlyChangedItem(lease));
		registrant.setLastUpdatedTimestamp();

        // 并使得二级缓存中的该实例失效
		invalidateCache(registrant.getAppName(), registrant.getVIPAddress(), registrant.getSecureVipAddress());
		logger.info("Registered instance {}/{} with status {} (replication={})",
				registrant.getAppName(), registrant.getId(), registrant.getStatus(), isReplication);
	} finally {
		read.unlock();
	}
}


private void invalidateCache(String appName, @Nullable String vipAddress, @Nullable String secureVipAddress) {
	// invalidate cache
	responseCache.invalidate(appName, vipAddress, secureVipAddress);
}

5)寻找一个服务

类:ResponseCacheImpl

Value getValue(final Key key, boolean useReadOnlyCache) {
	Value payload = null;
	try {
		if (useReadOnlyCache) {
			final Value currentPayload = readOnlyCacheMap.get(key);
			if (currentPayload != null) {
				payload = currentPayload;
			} else {
				payload = readWriteCacheMap.get(key);
				readOnlyCacheMap.put(key, payload);
			}
		} else {
			payload = readWriteCacheMap.get(key);
		}
	} catch (Throwable t) {
		logger.error("Cannot get value for key : {}", key, t);
	}
	return payload;
}

从三级缓存中找,如果有则返回;如果没有则去二级缓存拿并更新;如果二级缓存已经失效,触发guava的回调函数从注册表中同步。

6)数据同步定时器

每 30s 从二级缓存向三级缓存同步数据。 

类:ResponseCacheImpl

private TimerTask getCacheUpdateTask() {
	return new TimerTask() {
		@Override
		public void run() {
			logger.debug("Updating the client cache from response cache");
			for (Key key : readOnlyCacheMap.keySet()) {
				if (logger.isDebugEnabled()) {
					logger.debug("Updating the client cache from response cache for key : {} {} {} {}",
							key.getEntityType(), key.getName(), key.getVersion(), key.getType());
				}
				try {
					CurrentRequestVersion.set(key.getVersion());
					Value cacheValue = readWriteCacheMap.get(key);
					Value currentCacheValue = readOnlyCacheMap.get(key);
					if (cacheValue != currentCacheValue) {
						readOnlyCacheMap.put(key, cacheValue);
					}
				} catch (Throwable th) {
					logger.error("Error while updating the client cache from response cache for key {}", key.toStringCompact(), th);
				}
			}
		}
	};
}

 

七、Eureka 自我保护机制

自我保护机制是为了避免因网络分区故障而导致服务不可用的问题。具体现象为当网络故障后,所有的服务与 Eureka Server 之间无法进行正常通信,一定时间后,Eureka Server 没有收到续约的信息,将会移除没有续约的实例,这个时候正常的服务也会被移除掉,所以需要引入自我保护机制来解决这种问题。

如上图所示,当服务生产者出现网络故障,无法与 Eureka Server 进行续约,Eureka Server 会将该实例移除,此时服务消费者从 Eureka Server 拉取不到对应的信息,实际上服务生产者处于可用的状态,问题就是这样产生的。

如上图所示,再来看已开启自我保护,当服务生产者出现网络故障,无法与 Eureka Server 进行续约时,虽然 Eureka Server 开启了自我保护模式,但没有将该实例移除,服务消费者还是可以正常拉取服务生产者的信息,正常发起调用,但是如果服务生产者确实是下线状态,调用就会出错。
 

如果 Eureka Server 接收到的服务续约低于该值配置的百分比(默认 85%),则服务器开启自动保护模式,不再剔除注册列表的信息。 源码解析:
在AbstractInstanceRegistry类中,两个关键参数:

// 最小每分钟续租次数(开启自我保护的阈值)
protected volatile int numberOfRenewsPerMinThreshold;

//最大每分钟续租次数
protected volatile int expectedNumberOfClientsSendingRenews;

protected void updateRenewsPerMinThreshold() {
	this.numberOfRenewsPerMinThreshold = (int) (this.expectedNumberOfClientsSendingRenews
			* (60.0 / serverConfig.getExpectedClientRenewalIntervalSeconds())
			* serverConfig.getRenewalPercentThreshold());
}

 

自我保护具体实现:

Eureka服务端在定时执行剔除方法时,使用isLeaseExpirationEnabled()方法根据这两个参数判断是否可执行。

#AbstractInstanceRegistry

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.
	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)
			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);
		}
	}
}


@Override
public boolean isLeaseExpirationEnabled() {
	if (!isSelfPreservationModeEnabled()) {
		// The self preservation mode is disabled, hence allowing the instances to expire.
		return true;
	}
	return numberOfRenewsPerMinThreshold > 0 && getNumOfRenewsInLastMin() > numberOfRenewsPerMinThreshold;
}

 

两个参数何时更新?

1.客户端调用服务端进行注册、下线操作时会重新计算这两个值;

类: AbstractInstanceRegistry

public void register(InstanceInfo registrant, int leaseDuration, boolean isReplication) {
	try {
		read.lock();
		Map<String, Lease<InstanceInfo>> gMap = registry.get(registrant.getAppName());
		REGISTER.increment(isReplication);
		if (gMap == null) {
			final ConcurrentHashMap<String, Lease<InstanceInfo>> gNewMap = new ConcurrentHashMap<String, Lease<InstanceInfo>>();
			gMap = registry.putIfAbsent(registrant.getAppName(), gNewMap);
			if (gMap == null) {
				gMap = gNewMap;
			}
		}
		Lease<InstanceInfo> existingLease = gMap.get(registrant.getId());
		// Retain the last dirty timestamp without overwriting it, if there is already a lease
		if (existingLease != null && (existingLease.getHolder() != null)) {
			Long existingLastDirtyTimestamp = existingLease.getHolder().getLastDirtyTimestamp();
			Long registrationLastDirtyTimestamp = registrant.getLastDirtyTimestamp();
			logger.debug("Existing lease found (existing={}, provided={}", existingLastDirtyTimestamp, registrationLastDirtyTimestamp);

			// this is a > instead of a >= because if the timestamps are equal, we still take the remote transmitted
			// InstanceInfo instead of the server local copy.
			if (existingLastDirtyTimestamp > registrationLastDirtyTimestamp) {
				logger.warn("There is an existing lease and the existing lease's dirty timestamp {} is greater" +
						" than the one that is being registered {}", existingLastDirtyTimestamp, registrationLastDirtyTimestamp);
				logger.warn("Using the existing instanceInfo instead of the new instanceInfo as the registrant");
				registrant = existingLease.getHolder();
			}
		} else {
			// The lease does not exist and hence it is a new registration
			synchronized (lock) {
                // 服务注册,增加续约的客户端的数量
				if (this.expectedNumberOfClientsSendingRenews > 0) {
					// Since the client wants to register it, increase the number of clients sending renews
					this.expectedNumberOfClientsSendingRenews = this.expectedNumberOfClientsSendingRenews + 1;
                    // 更新服务续约的阈值
					updateRenewsPerMinThreshold();
				}
			}
			logger.debug("No previous lease information found; it is new registration");
		}

 2.定时任务scheduleRenewalThresholdUpdateTask每15分钟更新这两个值。

类:PeerAwareInstanceRegistryImpl

/**
 * Schedule the task that updates <em>renewal threshold</em> periodically.
 * The renewal threshold would be used to determine if the renewals drop
 * dramatically because of network partition and to protect expiring too
 * many instances at a time.
 *
 */
private void scheduleRenewalThresholdUpdateTask() {
	timer.schedule(new TimerTask() {
					   @Override
					   public void run() {
						   updateRenewalThreshold();
					   }
				   }, serverConfig.getRenewalThresholdUpdateIntervalMs(),
			serverConfig.getRenewalThresholdUpdateIntervalMs());
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值