本文为eureka学习笔记,错误之处请指正。
-----------------------------------------------------------
@Configuration @EurekaClientAutoConfiguration.ConditionalOnMissingRefreshScope protected static class EurekaClientConfiguration { //省略部分代码 @Bean(destroyMethod = "shutdown") @ConditionalOnMissingBean(value = {EurekaClient.class},search = SearchStrategy.CURRENT) public EurekaClient eurekaClient(ApplicationInfoManager manager, EurekaClientConfig config) {//用于跟服务器交互的客户端实例,实际是DiscoveryClient的一个子类(spring cloud对eureka自己进行了一些封装) return new CloudEurekaClient(manager, config, this.optionalArgs, this.context); } @Bean @ConditionalOnMissingBean( value = {ApplicationInfoManager.class}, search = SearchStrategy.CURRENT ) //在没有ApplicationInfoManager的实例的时候创建 public ApplicationInfoManager eurekaApplicationInfoManager(EurekaInstanceConfig config) { InstanceInfo instanceInfo = (new InstanceInfoFactory()).create(config); //在这里创建了instanceInfo return new ApplicationInfoManager(config, instanceInfo); } }
public InstanceInfo create(EurekaInstanceConfig config) {
Builder leaseInfoBuilder = Builder.newBuilder().setRenewalIntervalInSecs(config.getLeaseRenewalIntervalInSeconds()).setDurationInSecs(config.getLeaseExpirationDurationInSeconds());
com.netflix.appinfo.InstanceInfo.Builder builder = com.netflix.appinfo.InstanceInfo.Builder.newBuilder();
builder.setNamespace(namespace).setAppName(config.getAppname()) // 这里用config的信息进行了大量的设置
.setInstanceId(config.getInstanceId())
.setAppGroupName(config.getAppGroupName())
.setDataCenterInfo(config.getDataCenterInfo())
.setIPAddr(config.getIpAddress()).setHostName(config.getHostName(false))
.setPort(config.getNonSecurePort())
.enablePort(InstanceInfo.PortType.UNSECURE,
config.isNonSecurePortEnabled())
.setSecurePort(config.getSecurePort())
.enablePort(InstanceInfo.PortType.SECURE, config.getSecurePortEnabled())
.setVIPAddress(config.getVirtualHostName())
.setSecureVIPAddress(config.getSecureVirtualHostName())
.setHomePageUrl(config.getHomePageUrlPath(), config.getHomePageUrl())
.setStatusPageUrl(config.getStatusPageUrlPath(),
config.getStatusPageUrl())
.setHealthCheckUrls(config.getHealthCheckUrlPath(),
config.getHealthCheckUrl(), config.getSecureHealthCheckUrl())
.setASGName(config.getASGName());
// 省略代码若干...
return instanceInfo;
}
其中,方法参数EurekaInstanceConfig的实际实现是EurekaInstanceConfigBean,类注解如下:
@ConfigurationProperties("eureka.instance")//读取配置文件中前缀为eureka.instance的内容
public class EurekaInstanceConfigBean implements CloudEurekaInstanceConfig, EnvironmentAware {
private static final String UNKNOWN = "unknown";
private HostInfo hostInfo;
private InetUtils inetUtils;
......
}
if (clientConfig.shouldRegisterWithEureka()) {
int renewalIntervalInSecs = instanceInfo.getLeaseInfo().getRenewalIntervalInSecs();
int expBackOffBound = clientConfig.getHeartbeatExecutorExponentialBackOffBound();
logger.info("Starting heartbeat executor: " + "renew interval is: " + renewalIntervalInSecs);
// Heartbeat timer
scheduler.schedule( //心跳检测的代码
new TimedSupervisorTask(
"heartbeat",
scheduler,
heartbeatExecutor,
renewalIntervalInSecs,
TimeUnit.SECONDS,
expBackOffBound,
new HeartbeatThread()
),
renewalIntervalInSecs, TimeUnit.SECONDS);
//省略代码若干......
if (clientConfig.shouldOnDemandUpdateStatusChange()) {
applicationInfoManager.registerStatusChangeListener(statusChangeListener);
}
instanceInfoReplicator.start(clientConfig.getInitialInstanceInfoReplicationIntervalSeconds());
}
我们看到该方法启动了一个线程,new HeartbeatThread的run方法代码如下:
private class HeartbeatThread implements Runnable { public void run() { if (renew()) { //这个renew实际就是心跳检测的过程 lastSuccessfulHeartbeatTimestamp = System.currentTimeMillis(); } } }
renew方法代码:
boolean renew() { EurekaHttpResponse<InstanceInfo> httpResponse; try { httpResponse = eurekaTransport.registrationClient.sendHeartBeat(instanceInfo.getAppName(), instanceInfo.getId(), instanceInfo, null);//发送心跳请求 logger.debug("{} - Heartbeat status: {}", PREFIX + appPathIdentifier, httpResponse.getStatusCode()); if (httpResponse.getStatusCode() == 404) {//返回状态码400,不存在,说明服务端没有信息,要进行注册操作 REREGISTER_COUNTER.increment(); logger.info("{} - Re-registering apps/{}", PREFIX + appPathIdentifier, instanceInfo.getAppName()); return register();//进行注册 } return httpResponse.getStatusCode() == 200;//服务端正常返回,心跳检测成功 } catch (Throwable e) { logger.error("{} - was unable to send heartbeat!", PREFIX + appPathIdentifier, e); return false; } }
spring cloud中eureka的client跟server端都使用jersey进行交互,在server端,EurekaServerAutoConfiguration中的jerseyFilterRegistration方法动态注册了一个Jersey filter,用来拦截所有请求。具体对请求的处理是由ApplicationResource类的addInstance方法来完成的。
@POST @Consumes({"application/json", "application/xml"}) public Response addInstance(InstanceInfo info, @HeaderParam(PeerEurekaNode.HEADER_REPLICATION) String isReplication) { logger.debug("Registering instance {} (replication={})", info.getId(), isReplication); // validate that the instanceinfo contains all the necessary required fields //参数校验代码 // handle cases where clients may be registering with bad DataCenterInfo with missing data //高可用部分代码 registry.register(info, "true".equals(isReplication)); return Response.status(204).build(); // 204 to be backwards compatible }
父类InstanceRegistry的register是具体的执行代码,这个register方法干了两件事儿:1、发布了一个事件,2、进行了一个注册操作。再父类PeerAwareInstanceRegistryImpl的主要功能是将注册操作分发到eureka server集群的其它节点,以保持数据的一致性。继续一步步点击进入,最终的注册由AbstractInstanceRegistry类的register完成,部分代码如下:
private final ConcurrentHashMap<String, Map<String, Lease<InstanceInfo>>> registry = new ConcurrentHashMap<String, Map<String, Lease<InstanceInfo>>>(); //这个是map里边套了个map,键为服务名称 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>>();//这个是内部的map,键为示例名称,值为实例的具体信息 gMap = registry.putIfAbsent(registrant.getAppName(), gNewMap); if (gMap == null) { gMap = gNewMap; } } //其它代码 }
可以看到,最终服务端维护了一个ConcurrentHashMap,key为服务名值为该服务的实例组成的一个map,子map中key为实例名。
@PostConstruct
@Override
public void initialize() throws Exception {
logger.info("Initializing ...");
peerEurekaNodes.start(); //服务启动,刷新peer集群列表并启动更新任务
registry.init(peerEurekaNodes);
logger.info("Initialized");
}
其中start方法主要是启动了一个线程,每隔一段时间执行一次updatePeerEurekaNodes(List<String> newPeerUrls)方法,该方法主要作用是更新集群节点信息,添加新节点,删除关闭的节点。
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); //复制注册信息到其它节点,isReplication用于防止复制自身,造成集群间节点相互复制
}
当然,信息的同步过程,不止在注册发生,在服务的续约(renew),下架(cancle)均会进行同步。
public void evict(long additionalLeaseMs) {
//其它代码
// 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);
}
}
}
}
//其它代码
}
public boolean isExpired(long additionalLeaseMs) { return (evictionTimestamp > 0 || System.currentTimeMillis() > (lastUpdateTimestamp + duration + additionalLeaseMs)); //最后更新时间+lease的存活时间+补偿时间< 当前时间 }
private boolean fetchRegistry(boolean forceFullRegistryFetch) { Stopwatch tracer = FETCH_REGISTRY_TIMER.start(); try { // If the delta is disabled or if it is the first time, get all applications Applications applications = getApplications(); if (clientConfig.shouldDisableDelta() || (!Strings.isNullOrEmpty(clientConfig.getRegistryRefreshSingleVipAddress())) || forceFullRegistryFetch || (applications == null) || (applications.getRegisteredApplications().size() == 0) || (applications.getVersion() == -1)) {//Client application does not have latest library supporting delta logger.info("Disable delta property : {}", clientConfig.shouldDisableDelta()); logger.info("Single vip registry refresh property : {}", clientConfig.getRegistryRefreshSingleVipAddress()); logger.info("Force full registry fetch : {}", forceFullRegistryFetch); logger.info("Application is null : {}", (applications == null)); logger.info("Registered Applications size is zero : {}", (applications.getRegisteredApplications().size() == 0)); logger.info("Application version is -1: {}", (applications.getVersion() == -1)); getAndStoreFullRegistry();//全量数据 } else { getAndUpdateDelta(applications);//增量数据 } applications.setAppsHashCode(applications.getReconcileHashCode()); logTotalInstances(); } catch (Throwable e) { logger.error(PREFIX + appPathIdentifier + " - was unable to refresh its cache! status = " + e.getMessage(), e); return false; } finally { if (tracer != null) { tracer.stop(); } } // Notify about cache refresh before updating the instance remote status onCacheRefreshed(); // Update remote status based on refreshed data held in the cache updateInstanceRemoteStatus(); // registry was fetched successfully, so return true return true; }
protected <R> EurekaHttpResponse<R> execute(RequestExecutor<R> requestExecutor) {
long now = System.currentTimeMillis();
long delay = now - lastReconnectTimeStamp;
if (delay >= currentSessionDurationMs) { //这里判断超时就创建新的session,实际是定期创建session,目的是防止一个客户端一直连接某个server,在server增加时不利于负载均衡的处理
logger.debug("Ending a session and starting anew");
lastReconnectTimeStamp = now;
currentSessionDurationMs = randomizeSessionDuration(sessionDurationMs);
TransportUtils.shutdown(eurekaHttpClientRef.getAndSet(null));
}
//省略代码
return requestExecutor.execute(eurekaHttpClient); //还得进入看实现
}
private Applications filterAndShuffle(Applications apps) { if (apps != null) { if (isFetchingRemoteRegionRegistries()) { Map<String, Applications> remoteRegionVsApps = new ConcurrentHashMap<String, Applications>(); apps.shuffleAndIndexInstances(remoteRegionVsApps, clientConfig, instanceRegionChecker); for (Applications applications : remoteRegionVsApps.values()) { applications.shuffleInstances(clientConfig.shouldFilterOnlyUpInstances()); } this.remoteRegionVsApps = remoteRegionVsApps; } else { apps.shuffleInstances(clientConfig.shouldFilterOnlyUpInstances()); } } return apps; }
private class HeartbeatThread implements Runnable {
public void run() {
if (renew()) {//可以看到,就是调用了renew方法
lastSuccessfulHeartbeatTimestamp = System.currentTimeMillis();
}
}
}
----------------------------------------------