Eureka服务端源码解析 入口/注册/刷新/下线

Eureka服务端与客户端交互是通过发送http请求完成的. 使用JerseyClient进行服务间通讯, Jersey是一个RESTFUL请求服务JAVA框架, 与常规的JAVA编程使用的struts框架类似, 它主要用于处理业务逻辑层.

入口

在Server包中设置了启动类 spring.factor
org.springframework.boot.autoconfigure.EnableAutoConfiguration=
org.springframework.cloud.netflix.eureka.server.EurekaServerAutoConfiguration

EurekaServerAutoConfiguration注入了一些Bean 并且import配置类 @Import({EurekaServerInitializerConfiguration.class})
EurekaServerInitializerConfiguration实现了Spring的SmartLifecycle接口,在容器启动完成后会调用start方法进行初始化

启动方法

org.springframework.cloud.netflix.eureka.server.EurekaServerInitializerConfiguration#start
public void start() {
	(new Thread(() -> {
		try {
			// 初始化Server
			this.eurekaServerBootstrap.contextInitialized(this.servletContext);
			log.info("Started Eureka Server");
			// 发布注册中心启动事件
			this.publish(new EurekaRegistryAvailableEvent(this.getEurekaServerConfig()));
			this.running = true;
			// 发布Server启动事件
			this.publish(new EurekaServerStartedEvent(this.getEurekaServerConfig()));
		} catch (Exception var2) {
			log.error("Could not initialize Eureka servlet context", var2);
		}

	})).start();
}

初始化

org.springframework.cloud.netflix.eureka.server.EurekaServerBootstrap#contextInitialized
public void contextInitialized(ServletContext context) {
	try {
		// 初始化环境
		this.initEurekaEnvironment();
		// 初始化上下文
		this.initEurekaServerContext();
		context.setAttribute(EurekaServerContext.class.getName(), this.serverContext);
	} catch (Throwable var3) {
		log.error("Cannot bootstrap eureka server :", var3);
		throw new RuntimeException("Cannot bootstrap eureka server :", var3);
	}
}

protected void initEurekaEnvironment() throws Exception {
	log.info("Setting the eureka configuration..");
	// eureka.datacenter 指定数据中心 配置-Deureka.datacenter=cloud,这样eureka将会知道是在AWS云上
	String dataCenter = ConfigurationManager.getConfigInstance().getString("eureka.datacenter");
	if (dataCenter == null) {
		log.info("Eureka data center value eureka.datacenter is not set, defaulting to default");
		ConfigurationManager.getConfigInstance().setProperty("archaius.deployment.datacenter", "default");
	} else {
		ConfigurationManager.getConfigInstance().setProperty("archaius.deployment.datacenter", dataCenter);
	}
	// eureka.environment 指定环境
	String environment = ConfigurationManager.getConfigInstance().getString("eureka.environment");
	if (environment == null) {
		ConfigurationManager.getConfigInstance().setProperty("archaius.deployment.environment", "test");
		log.info("Eureka environment value eureka.environment is not set, defaulting to test");
	} else {
		ConfigurationManager.getConfigInstance().setProperty("archaius.deployment.environment", environment);
	}
}

protected void initEurekaServerContext() throws Exception {
	JsonXStream.getInstance().registerConverter(new V1AwareInstanceInfoConverter(), 10000);
	XmlXStream.getInstance().registerConverter(new V1AwareInstanceInfoConverter(), 10000);
	// 是否为aws数据中心
	if (this.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");
	// 从其他的注册中心同步数据
	int registryCount = this.registry.syncUp();
	// 发送心跳30s一次
	this.registry.openForTraffic(this.applicationInfoManager, registryCount);
	// 注册监控
	EurekaMonitors.registerAllStats();
}

同步信息

com.netflix.eureka.registry.PeerAwareInstanceRegistryImpl#syncUp
public int syncUp() {
	// Copy entire entry from neighboring DS node
	int count = 0;

	for (int i = 0; ((i < serverConfig.getRegistrySyncRetries()) && (count == 0)); i++) {
		if (i > 0) {
			try {
				Thread.sleep(serverConfig.getRegistrySyncRetryWaitMs());
			} catch (InterruptedException e) {
				logger.warn("Interrupted during registry transfer..");
				break;
			}
		}
		Applications apps = eurekaClient.getApplications();
		for (Application app : apps.getRegisteredApplications()) {
			for (InstanceInfo instance : app.getInstances()) {
				try {
					if (isRegisterable(instance)) {
						register(instance, instance.getLeaseInfo().getDurationInSecs(), true); // 本地注册
						count++;
					}
				} catch (Throwable t) {
					logger.error("During DS init copy", t);
				}
			}
		}
	}
	return count;
}


com.netflix.eureka.registry.AbstractInstanceRegistry#register
// ConcurrentHashMap来保存各服务的节点信息 key为appName value为map value->key 127.0.0.1:8080 value->value 节点对象
private final ConcurrentHashMap<String, Map<String, Lease<InstanceInfo>>> registry = new ConcurrentHashMap<String, Map<String, Lease<InstanceInfo>>>();

public void register(InstanceInfo registrant, int leaseDuration, boolean isReplication) {
	try {
		read.lock();
		// 先获取对应APP有没有
		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;
			}
		}
		...
		// 存入map中
		gMap.put(registrant.getId(), lease);
		...
	} finally {
		read.unlock();
	}
}

剔除服务线程

com.netflix.eureka.registry.PeerAwareInstanceRegistryImpl#openForTraffic
public void openForTraffic(ApplicationInfoManager applicationInfoManager, int count) {
	// Renewals happen every 30 seconds and for a minute it should be a factor of 2.
	this.expectedNumberOfClientsSendingRenews = count;
	updateRenewsPerMinThreshold();
	logger.info("Got {} instances from neighboring DS node", count);
	logger.info("Renew threshold is: {}", numberOfRenewsPerMinThreshold);
	this.startupTime = System.currentTimeMillis();
	if (count > 0) {
		this.peerInstancesTransferEmptyOnStartup = false;
	}
	DataCenterInfo.Name selfName = applicationInfoManager.getInfo().getDataCenterInfo().getName();
	boolean isAws = Name.Amazon == selfName;
	if (isAws && serverConfig.shouldPrimeAwsReplicaConnections()) {
		logger.info("Priming AWS connections for all replicas..");
		primeAwsReplicas(applicationInfoManager);
	}
	logger.info("Changing status to UP");
	applicationInfoManager.setInstanceStatus(InstanceStatus.UP);
	// 开启服务剔除
	super.postInit();
}

com.netflix.eureka.registry.AbstractInstanceRegistry#postInit
protected void postInit() {
	renewsLastMin.start();
	if (evictionTaskRef.get() != null) {
		evictionTaskRef.get().cancel();
	}
	evictionTaskRef.set(new EvictionTask());
	evictionTimer.schedule(evictionTaskRef.get(),
			serverConfig.getEvictionIntervalTimerInMs(),
			serverConfig.getEvictionIntervalTimerInMs());
}

com.netflix.eureka.registry.AbstractInstanceRegistry.EvictionTask#run
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);
	}
}

public void evict(long additionalLeaseMs) {
	...
	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);
				}
			}
		}
	}
	...
	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++) {
			...
			// 随机取消服务
			internalCancel(appName, id, false);
		}
	}
}

com.netflix.eureka.registry.AbstractInstanceRegistry#internalCancel
protected boolean internalCancel(String appName, String id, boolean isReplication) {
	try {
		read.lock();
		CANCEL.increment(isReplication);
		Map<String, Lease<InstanceInfo>> gMap = registry.get(appName);
		Lease<InstanceInfo> leaseToCancel = null;
		// 从map中删除服务地址 有剔除数量限制 避免崩溃
		if (gMap != null) {
			leaseToCancel = gMap.remove(id);
		}
		...
	} finally {
		read.unlock();
	}
}

注册服务

com.netflix.eureka.resources.ApplicationResource#addInstance
@POST
@Consumes({"application/json", "application/xml"})
public Response addInstance(InstanceInfo info,
		@HeaderParam(PeerEurekaNode.HEADER_REPLICATION) String isReplication) {
	...
	registry.register(info, "true".equals(isReplication));
	...
}

com.netflix.eureka.registry.PeerAwareInstanceRegistryImpl#register
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();
	}
	// 向map中添加服务节点
	super.register(info, leaseDuration, isReplication);
	// 通知其他注册中心 变更数据
	replicateToPeers(Action.Register, info.getAppName(), info.getId(), info, null, isReplication);
}

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;
		}
		// 获取注册中心list
		for (final PeerEurekaNode node : peerEurekaNodes.getPeerEurekaNodes()) {
			// If the url represents this host, do not replicate to yourself.
			if (peerEurekaNodes.isThisMyUrl(node.getServiceUrl())) {
				continue;
			}
			// 发送Register消息
			replicateInstanceActionsToPeers(action, appName, id, info, newStatus, node);
		}
	} finally {
		tracer.stop();
	}
}

private void replicateInstanceActionsToPeers(Action action, String appName,
											 String id, InstanceInfo info, InstanceStatus newStatus,
											 PeerEurekaNode node) {
	try {
		InstanceInfo infoFromRegistry = null;
		CurrentRequestVersion.set(Version.V2);
		switch (action) {
			case Cancel:
				node.cancel(appName, id);
				break;
			case Heartbeat:
				InstanceStatus overriddenStatus = overriddenInstanceStatusMap.get(id);
				infoFromRegistry = getInstanceByAppAndId(appName, id, false);
				node.heartbeat(appName, id, infoFromRegistry, overriddenStatus, false);
				break;
			case Register: // 注册服务
				node.register(info);
				break;
			case StatusUpdate:
				infoFromRegistry = getInstanceByAppAndId(appName, id, false);
				node.statusUpdate(appName, id, newStatus, infoFromRegistry);
				break;
			case DeleteStatusOverride:
				infoFromRegistry = getInstanceByAppAndId(appName, id, false);
				node.deleteStatusOverride(appName, id, infoFromRegistry);
				break;
		}
	} catch (Throwable t) {
		logger.error("Cannot replicate information to {} for action {}", node.getServiceUrl(), action.name(), t);
	}
}

com.netflix.eureka.cluster.PeerEurekaNode#register
public void register(final InstanceInfo info) throws Exception {
	long expiryTime = System.currentTimeMillis() + getLeaseRenewalOf(info);
	batchingDispatcher.process(
			taskId("register", info),
			new InstanceReplicationTask(targetHost, Action.Register, info, null, true) {
				public EurekaHttpResponse<Void> execute() {
					return replicationClient.register(info);
				}
			},
			expiryTime
	);
}

com.netflix.discovery.shared.transport.jersey.AbstractJerseyEurekaHttpClient#register
public EurekaHttpResponse<Void> register(InstanceInfo info) {
	String urlPath = "apps/" + info.getAppName();
	ClientResponse response = null;
	try {
		// 使用jerseyClient
		Builder resourceBuilder = jerseyClient.resource(serviceUrl).path(urlPath).getRequestBuilder();
		addExtraHeaders(resourceBuilder);
		response = resourceBuilder
				.header("Accept-Encoding", "gzip")
				.type(MediaType.APPLICATION_JSON_TYPE)
				.accept(MediaType.APPLICATION_JSON)
				.post(ClientResponse.class, info); // 发送post请求
		return anEurekaHttpResponse(response.getStatus()).headers(headersOf(response)).build();
	} finally {
		if (logger.isDebugEnabled()) {
			logger.debug("Jersey HTTP POST {}/{} with instance {}; statusCode={}", serviceUrl, urlPath, info.getId(),
					response == null ? "N/A" : response.getStatus());
		}
		if (response != null) {
			response.close();
		}
	}
}

续租服务

com.netflix.eureka.resources.InstanceResource#renewLease
@PUT
public Response renewLease(
		@HeaderParam(PeerEurekaNode.HEADER_REPLICATION) String isReplication,
		@QueryParam("overriddenstatus") String overriddenStatus,
		@QueryParam("status") String status,
		@QueryParam("lastDirtyTimestamp") String lastDirtyTimestamp) {
	boolean isSuccess = registry.renew(app.getName(), id, isFromReplicaNode);
	...
}

com.netflix.eureka.registry.AbstractInstanceRegistry#renew
public boolean renew(String appName, String id, boolean isReplication) {
	RENEW.increment(isReplication);
	Map<String, Lease<InstanceInfo>> gMap = registry.get(appName);
	Lease<InstanceInfo> leaseToRenew = null;
	if (gMap != null) {
		leaseToRenew = gMap.get(id);
	}
	...
	// 更新 lastUpdateTimestamp
	leaseToRenew.renew();
}

下线服务

com.netflix.eureka.resources.InstanceResource#cancelLease
@DELETE
public Response cancelLease(
		@HeaderParam(PeerEurekaNode.HEADER_REPLICATION) String isReplication) {
	boolean isSuccess = registry.cancel(app.getName(), id, "true".equals(isReplication));
	...
}

com.netflix.eureka.registry.AbstractInstanceRegistry#internalCancel
protected boolean internalCancel(String appName, String id, boolean isReplication) {
	try {
		read.lock();
		CANCEL.increment(isReplication);
		Map<String, Lease<InstanceInfo>> gMap = registry.get(appName);
		Lease<InstanceInfo> leaseToCancel = null;
		if (gMap != null) {
			leaseToCancel = gMap.remove(id); // 从map中移除节点
		}
		...
	} finally {
		read.unlock();
	}
}

Eureka缓存(三级)

Eureka为了应对高频繁的心跳, 做了一些缓存协助查询工作, 底层也是使用map, 只不过将结果缓存到一个只读map(readOnlyCacheMap), 每30s从readWriteCacheMap同步数据, 目的就是尽量减少读写并发, 提升查询的吞吐量.

readOnlyCacheMap

private final ConcurrentMap<Key, Value> readOnlyCacheMap = new ConcurrentHashMap<Key, Value>();

com.netflix.eureka.registry.ResponseCacheImpl#getValue
Value getValue(final Key key, boolean useReadOnlyCache) {
    Value payload = null;
    try {
        if (useReadOnlyCache) {
        	// 从readOnlyCacheMap中获取数据
            final Value currentPayload = readOnlyCacheMap.get(key);
            if (currentPayload != null) {
                payload = currentPayload;
            } else {
            	// 如果没有, 再从readWriteCacheMap获取
                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;
}30s同步数据task
com.netflix.eureka.registry.ResponseCacheImpl#getCacheUpdateTask
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);
                }
            }
        }
    };
}

readWriteCacheMap 180s后失效, 当失效后如果执行getCacheUpdateTask方法同步时, 会重新加载元素, 调用generatePayload方法, 执行了AbstractInstanceRegistry的getApplications方法, 而此方法是真正从注册表registry获取最新的服务注册信息, 最终加入到readWriteCacheMap中.

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

    long responseCacheUpdateIntervalMs = serverConfig.getResponseCacheUpdateIntervalMs();
    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;
                        }
                    });
	// 只读缓存 30s同步一次
    if (shouldUseReadOnlyResponseCache) {
        timer.schedule(getCacheUpdateTask(),
                new Date(((System.currentTimeMillis() / responseCacheUpdateIntervalMs) * responseCacheUpdateIntervalMs)
                        + responseCacheUpdateIntervalMs),
                responseCacheUpdateIntervalMs);
    }
    ...
}

// 最终都是通过registry.getApplicationsFromMultipleRegions方法返回对应的value
private Value generatePayload(Key key) {
    ...
    String payload;
    switch (key.getEntityType()) {
        case Application:
            boolean isRemoteRegionRequested = key.hasRegions();

            if (ALL_APPS.equals(key.getName())) {
                if (isRemoteRegionRequested) {
                    tracer = serializeAllAppsWithRemoteRegionTimer.start();
                    payload = getPayLoad(key, registry.getApplicationsFromMultipleRegions(key.getRegions()));
                } else {
                    tracer = serializeAllAppsTimer.start();
                    payload = getPayLoad(key, registry.getApplications());
                }
            } else if (ALL_APPS_DELTA.equals(key.getName())) {
                if (isRemoteRegionRequested) {
                    tracer = serializeDeltaAppsWithRemoteRegionTimer.start();
                    versionDeltaWithRegions.incrementAndGet();
                    versionDeltaWithRegionsLegacy.incrementAndGet();
                    payload = getPayLoad(key,
                            registry.getApplicationDeltasFromMultipleRegions(key.getRegions()));
                } else {
                    tracer = serializeDeltaAppsTimer.start();
                    versionDelta.incrementAndGet();
                    versionDeltaLegacy.incrementAndGet();
                    payload = getPayLoad(key, registry.getApplicationDeltas());
                }
            } else {
                tracer = serializeOneApptimer.start();
                payload = getPayLoad(key, registry.getApplication(key.getName()));
            }
            break;
        case VIP:
        case SVIP:
            tracer = serializeViptimer.start();
            payload = getPayLoad(key, getApplicationsForVip(key, registry));
            break;
        default:
            logger.error("Unidentified entity type: {} found in the cache key.", key.getEntityType());
            payload = "";
            break;
    }
    return new Value(payload);
    ...
}

点此跳转 Eureka客户端源码解析 注册/心跳/本地刷新/下线

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值