SpringCloud的server端之EurekaServerAutoConfiguration介绍

1.EurekaServer启动入口

EurekaServer的启动入口主要是通过EurekaServerAutoConfiguration这个启动类来完成的。它利用了SpringBoot的⾃动装配的特点,在spring-cloud-netflix-eureka-server的jar包中,有一个spring.factories的配置,如下图:
在这里插入图片描述
springboot应⽤启动时会加载EurekaServerAutoConfiguration⾃动配置类,下面我们就重点说一下EurekaServerAutoConfiguration这个配置类。

2.EurekaServerAutoConfiguration

我们从它的源码中,从上往下看,先看它的头信息:
package org.springframework.cloud.netflix.eureka.server;

@Configuration(
    proxyBeanMethods = false
)
@Import({EurekaServerInitializerConfiguration.class})//引入初始化加载启动类
@ConditionalOnBean({Marker.class})//自动装配的前提条件是要有一个Maker的bean
@EnableConfigurationProperties({EurekaDashboardProperties.class, InstanceRegistryProperties.class})
@PropertySource({"classpath:/eureka/server.properties"})
public class EurekaServerAutoConfiguration implements WebMvcConfigurer {
}

1.Marker

要想自动装配 EurekaServe,那就必须在容器中存在一个Marker的实例bean。Marker这个类是在EurekaServerMarkerConfiguration.java下:

@Configuration(
    proxyBeanMethods = false
)
public class EurekaServerMarkerConfiguration {
    public EurekaServerMarkerConfiguration() {
    }

    @Bean
    public EurekaServerMarkerConfiguration.Marker eurekaServerMarkerBean() {
        return new EurekaServerMarkerConfiguration.Marker();
    }
    class Marker {
        Marker() {
        }
    }
}

而EurekaServerMarkerConfiguration是由注解@EnableEurekaServer引入的:

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import({EurekaServerMarkerConfiguration.class})
public @interface EnableEurekaServer {
}

所有只有添加了@EnableEurekaServer注解,这个应用才会成为一个EurekaServer的服务。

2.EurekaServerInitializerConfiguration

在EurekaServerAutoConfiguration中通过@Import引入了EurekaServerInitializerConfiguration这个类,下面我们就通过debug跟一下它的启动过程。
EurekaServerInitializerConfiguration.java实现了SmartLifecycle接口,实现该接口,可以在spring容器构建后 执行一些start()方法,所以它里面的start方法就会先启动,我们看下它的start的方法:

public void start() {
    (new Thread(() -> {
        try {
            this.eurekaServerBootstrap.contextInitialized(this.servletContext);
            log.info("Started Eureka Server");
            this.publish(new EurekaRegistryAvailableEvent(this.getEurekaServerConfig()));
            this.running = true;
            this.publish(new EurekaServerStartedEvent(this.getEurekaServerConfig()));
        } catch (Exception var2) {
            log.error("Could not initialize Eureka servlet context", var2);
        }

    })).start();
}

我们重点看一下this.eurekaServerBootstrap.contextInitialized(this.servletContext)这个方法:

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

这里面有两个方法:
1.this.initEurekaEnvironment();主要是初始化erureka的环境,没有什么重要的内容;
2.this.initEurekaServerContext();初始化eurekaServer的上下文信息,我们重点跟一下这个方法。

protected void initEurekaServerContext() throws Exception {
    JsonXStream.getInstance().registerConverter(new V1AwareInstanceInfoConverter(), 10000);
    XmlXStream.getInstance().registerConverter(new V1AwareInstanceInfoConverter(), 10000);
    if (this.isAws(this.applicationInfoManager.getInfo())) {
        this.awsBinder = new AwsBinderDelegate(this.eurekaServerConfig, this.eurekaClientConfig, this.registry, this.applicationInfoManager);
        this.awsBinder.start();
    }

    //为非IOC容器提供获取serverContext对象接口
    EurekaServerContextHolder.initialize(this.serverContext);
    log.info("Initialized server context");
    //从集群中其他的EurekaServer中拉取注册表,返回的是注册表中的实例的个数
    //这里要注意一下:这个地方是有参数配置的,只有满足下面两个条件,才会真正的取拉取注册表:
    // 1.eureka.client.fetch-registry=true
    // 2.eureka.server.registry-sync-retries的数值至少要大于1 
    //这两个条件有一个不满足,都不会在启动时拉取的,否则就只能等客户端自己来续约
    int registryCount = this.registry.syncUp();
    //将当前的EurekaServer服务状态改成UP上线可用状态
    this.registry.openForTraffic(this.applicationInfoManager, registryCount);
    EurekaMonitors.registerAllStats();
}

下面重点看下this.registry.syncUp();这个方法:

public int syncUp() {
        int count = 0;
        //重试次数,如果上一次没有拉取成功,就重试,配置参数是eureka.server.registry-sync-retries
        for(int i = 0; i < this.serverConfig.getRegistrySyncRetries() && count == 0; ++i) {
            if (i > 0) {
                try {
                    Thread.sleep(this.serverConfig.getRegistrySyncRetryWaitMs());
                } catch (InterruptedException var10) {
                    logger.warn("Interrupted during registry transfer..");
                    break;
                }
            }

            Applications apps = this.eurekaClient.getApplications();
            Iterator var4 = apps.getRegisteredApplications().iterator();

            while(var4.hasNext()) {
                Application app = (Application)var4.next();
                Iterator var6 = app.getInstances().iterator();

                while(var6.hasNext()) {
                //把从远程获取到的注册信息,注册到自己的注册表中(map)
                    InstanceInfo instance = (InstanceInfo)var6.next();

                    try {
                        if (this.isRegisterable(instance)) {
                            this.register(instance, instance.getLeaseInfo().getDurationInSecs(), true);
                            ++count;
                        }
                    } catch (Throwable var9) {
                        logger.error("During DS init copy", var9);
                    }
                }
            }
        }
        return count;
    }

这里要特别清楚一点,这里的注册中心是从集群中的其他服务中拉取,而不是zookeeper那样,直接由主同步给从,所以这里没有做到强一致性,就是CAP中没有做到C,即没有满足一致性。
下面我们再跟一下this.registry.openForTraffic(this.applicationInfoManager, registryCount);这个方法:

public void openForTraffic(ApplicationInfoManager applicationInfoManager, int count) {
        this.expectedNumberOfClientsSendingRenews = count;
        this.updateRenewsPerMinThreshold();
        logger.info("Got {} instances from neighboring DS node", count);
        logger.info("Renew threshold is: {}", this.numberOfRenewsPerMinThreshold);
        this.startupTime = System.currentTimeMillis();
        if (count > 0) {
            this.peerInstancesTransferEmptyOnStartup = false;
        }

        Name selfName = applicationInfoManager.getInfo().getDataCenterInfo().getName();
        boolean isAws = Name.Amazon == selfName;
        if (isAws && this.serverConfig.shouldPrimeAwsReplicaConnections()) {
            logger.info("Priming AWS connections for all replicas..");
            this.primeAwsReplicas(applicationInfoManager);
        }

        logger.info("Changing status to UP");
        // 将当前的EurekaServer上线
        applicationInfoManager.setInstanceStatus(InstanceStatus.UP);
        super.postInit();
    }

这个方法最下面有一个super.postInit();我们进去看下:

protected void postInit() {
    this.renewsLastMin.start();
    //剔除的任务
    if (this.evictionTaskRef.get() != null) {
        ((AbstractInstanceRegistry.EvictionTask)this.evictionTaskRef.get()).cancel();
    }

    this.evictionTaskRef.set(new AbstractInstanceRegistry.EvictionTask());
    //来定时执行踢出任务,对注册表的一些服务进行定时踢出,即每隔多少毫秒执行一次
    this.evictionTimer.schedule((TimerTask)this.evictionTaskRef.get(), this.serverConfig.getEvictionIntervalTimerInMs(), this.serverConfig.getEvictionIntervalTimerInMs());
}

这里的踢出的时间间隔,是由下面这个参数配置的:eureka.server.eviction-interval-timer-in-ms来设置的,默认是60000L毫秒,即1分钟。
那么下面我看下这个剔除任务EvictionTask:

class EvictionTask extends TimerTask {
        private final AtomicLong lastExecutionNanosRef = new AtomicLong(0L);

        EvictionTask() {
        }

        public void run() {
            try {
                long compensationTimeMs = this.getCompensationTimeMs();
                AbstractInstanceRegistry.logger.info("Running the evict task with compensationTime {}ms", compensationTimeMs);
                //进行剔除操作
                AbstractInstanceRegistry.this.evict(compensationTimeMs);
            } catch (Throwable var3) {
                AbstractInstanceRegistry.logger.error("Could not run the evict task", var3);
            }

        }

        long getCompensationTimeMs() {
            long currNanos = this.getCurrentTimeNano();
            long lastNanos = this.lastExecutionNanosRef.getAndSet(currNanos);
            if (lastNanos == 0L) {
                return 0L;
            } else {
                long elapsedMs = TimeUnit.NANOSECONDS.toMillis(currNanos - lastNanos);
                long compensationTime = elapsedMs - AbstractInstanceRegistry.this.serverConfig.getEvictionIntervalTimerInMs();
                return compensationTime <= 0L ? 0L : compensationTime;
            }
        }

        long getCurrentTimeNano() {
            return System.nanoTime();
        }
    }

我们就重点看下踢出AbstractInstanceRegistry.this.evict(compensationTimeMs);这个方法:

public void evict(long additionalLeaseMs) {
        logger.debug("Running the evict task");
        //是否要执行踢出操作的判断
        if (!this.isLeaseExpirationEnabled()) {
            logger.debug("DS: lease expiration is currently disabled.");
        } else {
            List<Lease<InstanceInfo>> expiredLeases = new ArrayList();
            Iterator var4 = this.registry.entrySet().iterator();

            while(true) {
                Map leaseMap;
                do {
                    if (!var4.hasNext()) {
                        int registrySize = (int)this.getLocalRegistrySize();
                        int registrySizeThreshold = (int)((double)registrySize * this.serverConfig.getRenewalPercentThreshold());
                        int evictionLimit = registrySize - registrySizeThreshold;
                        int toEvict = Math.min(expiredLeases.size(), evictionLimit);
                        if (toEvict > 0) {
                            logger.info("Evicting {} items (expired={}, evictionLimit={})", new Object[]{toEvict, expiredLeases.size(), evictionLimit});
                            Random random = new Random(System.currentTimeMillis());

                            for(int i = 0; i < toEvict; ++i) {
                                int next = i + random.nextInt(expiredLeases.size() - i);
                                Collections.swap(expiredLeases, i, next);
                                Lease<InstanceInfo> lease = (Lease)expiredLeases.get(i);
                                String appName = ((InstanceInfo)lease.getHolder()).getAppName();
                                String id = ((InstanceInfo)lease.getHolder()).getId();
                                EurekaMonitors.EXPIRED.increment();
                                logger.warn("DS: Registry: expired lease for {}/{}", appName, id);
                                this.internalCancel(appName, id, false);
                            }
                        }

                        return;
                    }

                    Entry<String, Map<String, Lease<InstanceInfo>>> groupEntry = (Entry)var4.next();
                    leaseMap = (Map)groupEntry.getValue();
                } while(leaseMap == null);

                Iterator var7 = leaseMap.entrySet().iterator();

                while(var7.hasNext()) {
                    Entry<String, Lease<InstanceInfo>> leaseEntry = (Entry)var7.next();
                    Lease<InstanceInfo> lease = (Lease)leaseEntry.getValue();
                    if (lease.isExpired(additionalLeaseMs) && lease.getHolder() != null) {
                        expiredLeases.add(lease);
                    }
                }
            }
        }
    }

这里的重点就在于踢除操作条件的判断,即isLeaseExpirationEnabled()这个方法:

public boolean isLeaseExpirationEnabled() {
//没有开启自我保护,即关闭自我保护,执行剔除服务
    if (!this.isSelfPreservationModeEnabled()) {
        return true;
    } else {
    //开启了自我保护
    //必须满足下面连个条件才能执行剔除任务
        return this.numberOfRenewsPerMinThreshold > 0 && this.getNumOfRenewsInLastMin() > (long)this.numberOfRenewsPerMinThreshold;
    }
}

这里我做了一个流程图,供大家参考下:
在这里插入图片描述

3. DefaultEurekaServerContext

说完了EurekaServerAutoConfiguration的头信息,下面我们看下代码中的

@Bean
@ConditionalOnMissingBean
public EurekaServerContext eurekaServerContext(ServerCodecs serverCodecs, PeerAwareInstanceRegistry registry, PeerEurekaNodes peerEurekaNodes) {
    return new DefaultEurekaServerContext(this.eurekaServerConfig, serverCodecs, registry, peerEurekaNodes, this.applicationInfoManager);
}

下面是DefaultEurekaServerContext的initialize()方法,即初始化启动方法。

@PostConstruct
public void initialize() {
    logger.info("Initializing ...");
    this.peerEurekaNodes.start();

    try {
        this.registry.init(this.peerEurekaNodes);
    } catch (Exception var2) {
        throw new RuntimeException(var2);
    }

    logger.info("Initialized");
}

我们再跟一下this.registry.init(this.peerEurekaNodes);

public void init(PeerEurekaNodes peerEurekaNodes) throws Exception {
    this.numberOfReplicationsLastMin.start();
    this.peerEurekaNodes = peerEurekaNodes;
    //缓存相关的初始化信息
    this.initializedResponseCache();
    //更新续约的阀值
    this.scheduleRenewalThresholdUpdateTask();
    this.initRemoteRegionRegistry();

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

}

我们重点说一下这里的缓存,先看下ResponseCacheImpl它的构造函数:

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, ResponseCacheImpl.Value>() {
            public void onRemoval(RemovalNotification<Key, ResponseCacheImpl.Value> notification) {
                Key removedKey = (Key)notification.getKey();
                if (removedKey.hasRegions()) {
                    Key cloneWithNoRegions = removedKey.cloneWithoutRegions();
                    ResponseCacheImpl.this.regionSpecificKeys.remove(cloneWithNoRegions, removedKey);
                }

            }
        }).build(new CacheLoader<Key, ResponseCacheImpl.Value>() {
            public ResponseCacheImpl.Value load(Key key) throws Exception {
                if (key.hasRegions()) {
                    Key cloneWithNoRegions = key.cloneWithoutRegions();
                    ResponseCacheImpl.this.regionSpecificKeys.put(cloneWithNoRegions, key);
                }

                ResponseCacheImpl.Value value = ResponseCacheImpl.this.generatePayload(key);
                return value;
            }
        });
        //是否开启缓存 配置参数:eureka.server.use-read-only-response-cache
        if (this.shouldUseReadOnlyResponseCache) {
        //如果开启缓存,缓存多少时间同步一次。配置参数:eureka.server.response-cache-update-interval-ms
            this.timer.schedule(this.getCacheUpdateTask(), new Date(System.currentTimeMillis() / responseCacheUpdateIntervalMs * responseCacheUpdateIntervalMs + responseCacheUpdateIntervalMs), responseCacheUpdateIntervalMs);
        }

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

    }

4.FilterRegistrationBean

下面我咱们再看一个EurekaServerAutoConfiguration的组件组件FilterRegistrationBean,对eurekaServer所有的操作都是通过http请求得来的。

@Bean
public FilterRegistrationBean<?> jerseyFilterRegistration(Application eurekaJerseyApp) {
    FilterRegistrationBean<Filter> bean = new FilterRegistrationBean();
    bean.setFilter(new ServletContainer(eurekaJerseyApp));
    bean.setOrder(2147483647);
    bean.setUrlPatterns(Collections.singletonList("/eureka/*"));
    return bean;
}

它的api请参考:https://github.com/Netflix/eureka/wiki/Eureka-REST-operations,里面有详细的介绍,我们可以通过postman来模拟服务的注册、健康检查(续约)、删除(下线)、获取注册表、获取某个注册实例的完整信息和集群间的同步
例子:
1.注册:POST http://localhost:7900/eureka/apps/MyApp02

<instance>
<instanceId>MyApp02</instanceId>
<hostName>localhost</hostName>
<app>MyApp02</app>
<ipAddr>127.0.0.1</ipAddr>
<status>UP</status>
<port enabled="true">8080</port>
<dataCenterInfo class="com.netflix.appinfo.InstanceInfo$DefaultDataCenterInfo">
<name>MyOwn</name>
</dataCenterInfo>
</instance>

在这里插入图片描述
2.健康检查(续约):PUT http://localhost:7900/eureka/apps/MyApp02/MyApp02

3.删除(下线):DELETE http://localhost:7900/eureka/apps/MyApp02/MyApp02
4.获取读物注册表:GET http://localhost:7901/eureka/apps/ http://localhost:7901/eureka/apps/MyApp02/

3.小结:

在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值