zoukankan      html  css  js  c++  java
  • EurekaServer 原理简单分析

      部署一个EurekaServer非常的简单,大致分为下面几步:

    (1) 倒入spring-cloud-starter-netflix-eureka-server 包

    (2) yml配置集群等信息

    server:
      port: 7001
    
    eureka:
      instance:
        hostname: eureka1.com #eureka服务端的实例名称
      client:
        register-with-eureka: false     #false表示不向注册中心注册自己。
        fetch-registry: false     #false表示自己端就是注册中心,我的职责就是维护服务实例,并不需要去检索服务
        service-url:
        #单机就是7001自己
    #      defaultZone: http://localhost:7001/eureka/
        #集群指向其它eureka
          defaultZone: http://localhost:7002/eureka/
    #  server:
        #服务端是否开启自我保护机制 (默认true)
    #    enable-self-preservation: false
        #扫描失效服务的间隔时间(单位毫秒,默认是60*1000)即60秒
    #    eviction-interval-timer-in-ms: 2000

    (3)然后主类上加上@SpringBootApplication、@EnableEurekaServer 注解即可。所以重点做处理的应该是第二个EnableSurekaServer注解。

      接下来研究其主要过程。

    1. 查看EnableEurekaServer注解

    package org.springframework.cloud.netflix.eureka.server;
    
    import java.lang.annotation.Documented;
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    import org.springframework.context.annotation.Import;
    
    @Target({ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Import({EurekaServerMarkerConfiguration.class})
    public @interface EnableEurekaServer {
    }

    接下来继续看Import进来的类:org.springframework.cloud.netflix.eureka.server.EurekaServerMarkerConfiguration

    package org.springframework.cloud.netflix.eureka.server;
    
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    
    @Configuration(
        proxyBeanMethods = false
    )
    public class EurekaServerMarkerConfiguration {
        public EurekaServerMarkerConfiguration() {
        }
    
        @Bean
        public EurekaServerMarkerConfiguration.Marker eurekaServerMarkerBean() {
            return new EurekaServerMarkerConfiguration.Marker();
        }
    
        class Marker {
            Marker() {
            }
        }
    }

      这个类也就是注入了一个空Marker类,可以iang这个Marker类视为一个标记类,其本身没有任何业务操作,只是做一个是否开启EurekaServer的标记。是org.springframework.cloud.netflix.eureka.server.EurekaServerAutoConfiguration 类在使用这个标记。

    2. org.springframework.cloud.netflix.eureka.server.EurekaServerAutoConfiguration

    如下: @ConditionalOnBean({Marker.class}) 使用上面的标记进行自动配置

    //
    // Source code recreated from a .class file by IntelliJ IDEA
    // (powered by Fernflower decompiler)
    //
    
    package org.springframework.cloud.netflix.eureka.server;
    
    import com.netflix.appinfo.ApplicationInfoManager;
    import com.netflix.discovery.EurekaClient;
    import com.netflix.discovery.EurekaClientConfig;
    import com.netflix.discovery.converters.EurekaJacksonCodec;
    import com.netflix.discovery.converters.wrappers.CodecWrapper;
    import com.netflix.discovery.converters.wrappers.CodecWrappers;
    import com.netflix.discovery.converters.wrappers.CodecWrappers.JacksonJsonMini;
    import com.netflix.discovery.converters.wrappers.CodecWrappers.JacksonXmlMini;
    import com.netflix.discovery.converters.wrappers.CodecWrappers.XStreamXml;
    import com.netflix.eureka.DefaultEurekaServerContext;
    import com.netflix.eureka.EurekaServerConfig;
    import com.netflix.eureka.EurekaServerContext;
    import com.netflix.eureka.cluster.PeerEurekaNode;
    import com.netflix.eureka.cluster.PeerEurekaNodes;
    import com.netflix.eureka.registry.PeerAwareInstanceRegistry;
    import com.netflix.eureka.resources.DefaultServerCodecs;
    import com.netflix.eureka.resources.ServerCodecs;
    import com.netflix.eureka.transport.JerseyReplicationClient;
    import com.sun.jersey.api.core.DefaultResourceConfig;
    import com.sun.jersey.spi.container.servlet.ServletContainer;
    import java.util.Collections;
    import java.util.HashMap;
    import java.util.HashSet;
    import java.util.Iterator;
    import java.util.Map;
    import java.util.Set;
    import java.util.function.Consumer;
    import javax.servlet.Filter;
    import javax.ws.rs.Path;
    import javax.ws.rs.core.Application;
    import javax.ws.rs.ext.Provider;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.beans.factory.annotation.Qualifier;
    import org.springframework.beans.factory.config.BeanDefinition;
    import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
    import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
    import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
    import org.springframework.boot.context.properties.EnableConfigurationProperties;
    import org.springframework.boot.web.servlet.FilterRegistrationBean;
    import org.springframework.cloud.client.actuator.HasFeatures;
    import org.springframework.cloud.context.environment.EnvironmentChangeEvent;
    import org.springframework.cloud.netflix.eureka.server.EurekaServerMarkerConfiguration.Marker;
    import org.springframework.context.ApplicationListener;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.context.annotation.Import;
    import org.springframework.context.annotation.PropertySource;
    import org.springframework.core.env.Environment;
    import org.springframework.core.io.ResourceLoader;
    import org.springframework.core.type.filter.AnnotationTypeFilter;
    import org.springframework.util.ClassUtils;
    import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
    
    @Configuration(
        proxyBeanMethods = false
    )
    @Import({EurekaServerInitializerConfiguration.class})
    @ConditionalOnBean({Marker.class})
    @EnableConfigurationProperties({EurekaDashboardProperties.class, InstanceRegistryProperties.class})
    @PropertySource({"classpath:/eureka/server.properties"})
    public class EurekaServerAutoConfiguration implements WebMvcConfigurer {
        private static final String[] EUREKA_PACKAGES = new String[]{"com.netflix.discovery", "com.netflix.eureka"};
        @Autowired
        private ApplicationInfoManager applicationInfoManager;
        @Autowired
        private EurekaServerConfig eurekaServerConfig;
        @Autowired
        private EurekaClientConfig eurekaClientConfig;
        @Autowired
        private EurekaClient eurekaClient;
        @Autowired
        private InstanceRegistryProperties instanceRegistryProperties;
        public static final CloudJacksonJson JACKSON_JSON = new CloudJacksonJson();
    
        public EurekaServerAutoConfiguration() {
        }
    
        @Bean
        public HasFeatures eurekaServerFeature() {
            return HasFeatures.namedFeature("Eureka Server", EurekaServerAutoConfiguration.class);
        }
    
        @Bean
        @ConditionalOnProperty(
            prefix = "eureka.dashboard",
            name = {"enabled"},
            matchIfMissing = true
        )
        public EurekaController eurekaController() {
            return new EurekaController(this.applicationInfoManager);
        }
    
        @Bean
        public ServerCodecs serverCodecs() {
            return new EurekaServerAutoConfiguration.CloudServerCodecs(this.eurekaServerConfig);
        }
    
        private static CodecWrapper getFullJson(EurekaServerConfig serverConfig) {
            CodecWrapper codec = CodecWrappers.getCodec(serverConfig.getJsonCodecName());
            return codec == null ? CodecWrappers.getCodec(JACKSON_JSON.codecName()) : codec;
        }
    
        private static CodecWrapper getFullXml(EurekaServerConfig serverConfig) {
            CodecWrapper codec = CodecWrappers.getCodec(serverConfig.getXmlCodecName());
            return codec == null ? CodecWrappers.getCodec(XStreamXml.class) : codec;
        }
    
        @Bean
        @ConditionalOnMissingBean
        public ReplicationClientAdditionalFilters replicationClientAdditionalFilters() {
            return new ReplicationClientAdditionalFilters(Collections.emptySet());
        }
    
        @Bean
        public PeerAwareInstanceRegistry peerAwareInstanceRegistry(ServerCodecs serverCodecs) {
            this.eurekaClient.getApplications();
            return new InstanceRegistry(this.eurekaServerConfig, this.eurekaClientConfig, serverCodecs, this.eurekaClient, this.instanceRegistryProperties.getExpectedNumberOfClientsSendingRenews(), this.instanceRegistryProperties.getDefaultOpenForTrafficCount());
        }
    
        @Bean
        @ConditionalOnMissingBean
        public PeerEurekaNodes peerEurekaNodes(PeerAwareInstanceRegistry registry, ServerCodecs serverCodecs, ReplicationClientAdditionalFilters replicationClientAdditionalFilters) {
            return new EurekaServerAutoConfiguration.RefreshablePeerEurekaNodes(registry, this.eurekaServerConfig, this.eurekaClientConfig, serverCodecs, this.applicationInfoManager, replicationClientAdditionalFilters);
        }
    
        @Bean
        public EurekaServerContext eurekaServerContext(ServerCodecs serverCodecs, PeerAwareInstanceRegistry registry, PeerEurekaNodes peerEurekaNodes) {
            return new DefaultEurekaServerContext(this.eurekaServerConfig, serverCodecs, registry, peerEurekaNodes, this.applicationInfoManager);
        }
    
        @Bean
        public EurekaServerBootstrap eurekaServerBootstrap(PeerAwareInstanceRegistry registry, EurekaServerContext serverContext) {
            return new EurekaServerBootstrap(this.applicationInfoManager, this.eurekaClientConfig, this.eurekaServerConfig, registry, serverContext);
        }
    
        @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;
        }
    
        @Bean
        public Application jerseyApplication(Environment environment, ResourceLoader resourceLoader) {
            ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(false, environment);
            provider.addIncludeFilter(new AnnotationTypeFilter(Path.class));
            provider.addIncludeFilter(new AnnotationTypeFilter(Provider.class));
            Set<Class<?>> classes = new HashSet();
            String[] var5 = EUREKA_PACKAGES;
            int var6 = var5.length;
    
            for(int var7 = 0; var7 < var6; ++var7) {
                String basePackage = var5[var7];
                Set<BeanDefinition> beans = provider.findCandidateComponents(basePackage);
                Iterator var10 = beans.iterator();
    
                while(var10.hasNext()) {
                    BeanDefinition bd = (BeanDefinition)var10.next();
                    Class<?> cls = ClassUtils.resolveClassName(bd.getBeanClassName(), resourceLoader.getClassLoader());
                    classes.add(cls);
                }
            }
    
            Map<String, Object> propsAndFeatures = new HashMap();
            propsAndFeatures.put("com.sun.jersey.config.property.WebPageContentRegex", "/eureka/(fonts|images|css|js)/.*");
            DefaultResourceConfig rc = new DefaultResourceConfig(classes);
            rc.setPropertiesAndFeatures(propsAndFeatures);
            return rc;
        }
    
        @Bean
        @ConditionalOnBean(
            name = {"httpTraceFilter"}
        )
        public FilterRegistrationBean<?> traceFilterRegistration(@Qualifier("httpTraceFilter") Filter filter) {
            FilterRegistrationBean<Filter> bean = new FilterRegistrationBean();
            bean.setFilter(filter);
            bean.setOrder(2147483637);
            return bean;
        }
    
        static {
            CodecWrappers.registerWrapper(JACKSON_JSON);
            EurekaJacksonCodec.setInstance(JACKSON_JSON.getCodec());
        }
    
        class CloudServerCodecs extends DefaultServerCodecs {
            CloudServerCodecs(EurekaServerConfig serverConfig) {
                super(EurekaServerAutoConfiguration.getFullJson(serverConfig), CodecWrappers.getCodec(JacksonJsonMini.class), EurekaServerAutoConfiguration.getFullXml(serverConfig), CodecWrappers.getCodec(JacksonXmlMini.class));
            }
        }
    
        static class RefreshablePeerEurekaNodes extends PeerEurekaNodes implements ApplicationListener<EnvironmentChangeEvent> {
            private ReplicationClientAdditionalFilters replicationClientAdditionalFilters;
    
            RefreshablePeerEurekaNodes(PeerAwareInstanceRegistry registry, EurekaServerConfig serverConfig, EurekaClientConfig clientConfig, ServerCodecs serverCodecs, ApplicationInfoManager applicationInfoManager, ReplicationClientAdditionalFilters replicationClientAdditionalFilters) {
                super(registry, serverConfig, clientConfig, serverCodecs, applicationInfoManager);
                this.replicationClientAdditionalFilters = replicationClientAdditionalFilters;
            }
    
            protected PeerEurekaNode createPeerEurekaNode(String peerEurekaNodeUrl) {
                JerseyReplicationClient replicationClient = JerseyReplicationClient.createReplicationClient(this.serverConfig, this.serverCodecs, peerEurekaNodeUrl);
                this.replicationClientAdditionalFilters.getFilters().forEach(replicationClient::addReplicationClientFilter);
                String targetHost = hostFromUrl(peerEurekaNodeUrl);
                if (targetHost == null) {
                    targetHost = "host";
                }
    
                return new PeerEurekaNode(this.registry, targetHost, peerEurekaNodeUrl, replicationClient, this.serverConfig);
            }
    
            public void onApplicationEvent(EnvironmentChangeEvent event) {
                if (this.shouldUpdate(event.getKeys())) {
                    this.updatePeerEurekaNodes(this.resolvePeerUrls());
                }
    
            }
    
            protected boolean shouldUpdate(Set<String> changedKeys) {
                assert changedKeys != null;
    
                if (this.clientConfig.shouldUseDnsForFetchingServiceUrls()) {
                    return false;
                } else if (changedKeys.contains("eureka.client.region")) {
                    return true;
                } else {
                    Iterator var2 = changedKeys.iterator();
    
                    String key;
                    do {
                        if (!var2.hasNext()) {
                            return false;
                        }
    
                        key = (String)var2.next();
                    } while(!key.startsWith("eureka.client.service-url.") && !key.startsWith("eureka.client.availability-zones."));
    
                    return true;
                }
            }
        }
    
        @Configuration(
            proxyBeanMethods = false
        )
        protected static class EurekaServerConfigBeanConfiguration {
            protected EurekaServerConfigBeanConfiguration() {
            }
    
            @Bean
            @ConditionalOnMissingBean
            public EurekaServerConfig eurekaServerConfig(EurekaClientConfig clientConfig) {
                EurekaServerConfigBean server = new EurekaServerConfigBean();
                if (clientConfig.shouldRegisterWithEureka()) {
                    server.setRegistrySyncRetries(5);
                }
    
                return server;
            }
        }
    }

    这里面挑几个重要的配置类信息进行说明:

    (1) 如下开启web 界面(也就是我们通过界面查看注册的服务等信息)

        @Bean
        @ConditionalOnProperty(
            prefix = "eureka.dashboard",
            name = {"enabled"},
            matchIfMissing = true
        )
        public EurekaController eurekaController() {
            return new EurekaController(this.applicationInfoManager);
        }

    EurekaController  就是接收web控制台请求的Controller,其中也内置了一些模板以及css、js 等静态资源

    @Controller
    @RequestMapping({"${eureka.dashboard.path:/}"})
    public class EurekaController {
        @Value("${eureka.dashboard.path:/}")
        private String dashboardPath = "";
        private ApplicationInfoManager applicationInfoManager;
    
        public EurekaController(ApplicationInfoManager applicationInfoManager) {
            this.applicationInfoManager = applicationInfoManager;
        }
    
        @RequestMapping(
            method = {RequestMethod.GET}
        )
        public String status(HttpServletRequest request, Map<String, Object> model) {
            this.populateBase(request, model);
            this.populateApps(model);
    
            StatusInfo statusInfo;
            try {
                statusInfo = (new StatusResource()).getStatusInfo();
            } catch (Exception var5) {
                statusInfo = Builder.newBuilder().isHealthy(false).build();
            }
    
            model.put("statusInfo", statusInfo);
            this.populateInstanceInfo(model, statusInfo);
            this.filterReplicas(model, statusInfo);
            return "eureka/status";
        }
    
        ...
    }

    静态资源如下:

     (2) 接下来注入了一个com.netflix.eureka.registry.PeerAwareInstanceRegistry,这个是集群模式下需要的注册器,集群模式下eureka的server节点都是对等的,没有主从之分

        @Bean
        public PeerAwareInstanceRegistry peerAwareInstanceRegistry(ServerCodecs serverCodecs) {
            this.eurekaClient.getApplications();
            return new InstanceRegistry(this.eurekaServerConfig, this.eurekaClientConfig, serverCodecs, this.eurekaClient, this.instanceRegistryProperties.getExpectedNumberOfClientsSendingRenews(), this.instanceRegistryProperties.getDefaultOpenForTrafficCount());
        }

    org.springframework.cloud.netflix.eureka.server.InstanceRegistry 类信息如下:

    package org.springframework.cloud.netflix.eureka.server;
    
    import com.netflix.appinfo.ApplicationInfoManager;
    import com.netflix.appinfo.InstanceInfo;
    import com.netflix.discovery.EurekaClient;
    import com.netflix.discovery.EurekaClientConfig;
    import com.netflix.discovery.shared.Application;
    import com.netflix.eureka.EurekaServerConfig;
    import com.netflix.eureka.registry.PeerAwareInstanceRegistryImpl;
    import com.netflix.eureka.resources.ServerCodecs;
    import java.util.Iterator;
    import java.util.List;
    import org.apache.commons.logging.Log;
    import org.apache.commons.logging.LogFactory;
    import org.springframework.beans.BeansException;
    import org.springframework.cloud.netflix.eureka.server.event.EurekaInstanceCanceledEvent;
    import org.springframework.cloud.netflix.eureka.server.event.EurekaInstanceRegisteredEvent;
    import org.springframework.cloud.netflix.eureka.server.event.EurekaInstanceRenewedEvent;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.ApplicationContextAware;
    import org.springframework.context.ApplicationEvent;
    
    public class InstanceRegistry extends PeerAwareInstanceRegistryImpl implements ApplicationContextAware {
        private static final Log log = LogFactory.getLog(InstanceRegistry.class);
        private ApplicationContext ctxt;
        private int defaultOpenForTrafficCount;
    
        public InstanceRegistry(EurekaServerConfig serverConfig, EurekaClientConfig clientConfig, ServerCodecs serverCodecs, EurekaClient eurekaClient, int expectedNumberOfClientsSendingRenews, int defaultOpenForTrafficCount) {
            super(serverConfig, clientConfig, serverCodecs, eurekaClient);
            this.expectedNumberOfClientsSendingRenews = expectedNumberOfClientsSendingRenews;
            this.defaultOpenForTrafficCount = defaultOpenForTrafficCount;
        }
    
        public void setApplicationContext(ApplicationContext context) throws BeansException {
            this.ctxt = context;
        }
    
        public void openForTraffic(ApplicationInfoManager applicationInfoManager, int count) {
            super.openForTraffic(applicationInfoManager, count == 0 ? this.defaultOpenForTrafficCount : count);
        }
    
        public void register(InstanceInfo info, int leaseDuration, boolean isReplication) {
            this.handleRegistration(info, leaseDuration, isReplication);
            super.register(info, leaseDuration, isReplication);
        }
    
        public void register(InstanceInfo info, boolean isReplication) {
            this.handleRegistration(info, this.resolveInstanceLeaseDuration(info), isReplication);
            super.register(info, isReplication);
        }
    
        public boolean cancel(String appName, String serverId, boolean isReplication) {
            this.handleCancelation(appName, serverId, isReplication);
            return super.cancel(appName, serverId, isReplication);
        }
    
        public boolean renew(String appName, String serverId, boolean isReplication) {
            this.log("renew " + appName + " serverId " + serverId + ", isReplication {}" + isReplication);
            List<Application> applications = this.getSortedApplications();
            Iterator var5 = applications.iterator();
    
            while(var5.hasNext()) {
                Application input = (Application)var5.next();
                if (input.getName().equals(appName)) {
                    InstanceInfo instance = null;
                    Iterator var8 = input.getInstances().iterator();
    
                    while(var8.hasNext()) {
                        InstanceInfo info = (InstanceInfo)var8.next();
                        if (info.getId().equals(serverId)) {
                            instance = info;
                            break;
                        }
                    }
    
                    this.publishEvent(new EurekaInstanceRenewedEvent(this, appName, serverId, instance, isReplication));
                    break;
                }
            }
    
            return super.renew(appName, serverId, isReplication);
        }
    
        protected boolean internalCancel(String appName, String id, boolean isReplication) {
            this.handleCancelation(appName, id, isReplication);
            return super.internalCancel(appName, id, isReplication);
        }
    
        private void handleCancelation(String appName, String id, boolean isReplication) {
            this.log("cancel " + appName + ", serverId " + id + ", isReplication " + isReplication);
            this.publishEvent(new EurekaInstanceCanceledEvent(this, appName, id, isReplication));
        }
    
        private void handleRegistration(InstanceInfo info, int leaseDuration, boolean isReplication) {
            this.log("register " + info.getAppName() + ", vip " + info.getVIPAddress() + ", leaseDuration " + leaseDuration + ", isReplication " + isReplication);
            this.publishEvent(new EurekaInstanceRegisteredEvent(this, info, leaseDuration, isReplication));
        }
    
        private void log(String message) {
            if (log.isDebugEnabled()) {
                log.debug(message);
            }
    
        }
    
        private void publishEvent(ApplicationEvent applicationEvent) {
            this.ctxt.publishEvent(applicationEvent);
        }
    
        private int resolveInstanceLeaseDuration(InstanceInfo info) {
            int leaseDuration = 90;
            if (info.getLeaseInfo() != null && info.getLeaseInfo().getDurationInSecs() > 0) {
                leaseDuration = info.getLeaseInfo().getDurationInSecs();
            }
    
            return leaseDuration;
        }
    }

    (3) 接下来注入一个PeerEurekaNodes, 这个是存放server集群中的对等节点相关的操作

        @Bean
        @ConditionalOnMissingBean
        public PeerEurekaNodes peerEurekaNodes(PeerAwareInstanceRegistry registry, ServerCodecs serverCodecs, ReplicationClientAdditionalFilters replicationClientAdditionalFilters) {
            return new EurekaServerAutoConfiguration.RefreshablePeerEurekaNodes(registry, this.eurekaServerConfig, this.eurekaClientConfig, serverCodecs, this.applicationInfoManager, replicationClientAdditionalFilters);
        }

    1》集群其他节点的相关信息存在org.springframework.cloud.netflix.eureka.EurekaClientConfigBean#serviceUrl 中,如下:

    private Map<String, String> serviceUrl = new HashMap();

    获取其他server的url方法org.springframework.cloud.netflix.eureka.EurekaClientConfigBean#getEurekaServerServiceUrls:

        public List<String> getEurekaServerServiceUrls(String myZone) {
            String serviceUrls = (String)this.serviceUrl.get(myZone);
            if (serviceUrls == null || serviceUrls.isEmpty()) {
                serviceUrls = (String)this.serviceUrl.get("defaultZone");
            }
    
            if (!StringUtils.isEmpty(serviceUrls)) {
                String[] serviceUrlsSplit = StringUtils.commaDelimitedListToStringArray(serviceUrls);
                List<String> eurekaServiceUrls = new ArrayList(serviceUrlsSplit.length);
                String[] var5 = serviceUrlsSplit;
                int var6 = serviceUrlsSplit.length;
    
                for(int var7 = 0; var7 < var6; ++var7) {
                    String eurekaServiceUrl = var5[var7];
                    if (!this.endsWithSlash(eurekaServiceUrl)) {
                        eurekaServiceUrl = eurekaServiceUrl + "/";
                    }
    
                    eurekaServiceUrls.add(eurekaServiceUrl.trim());
                }
    
                return eurekaServiceUrls;
            } else {
                return new ArrayList();
            }
        }

        可以看到用, 切割之后转为集合返回去。 可以看到地址如果不是以/  结尾会处理成以/ 结尾。

    2》com.netflix.eureka.cluster.PeerEurekaNodes 类有两个重要的属性以及三个方法用于对对等节点的存储以及刷新.

        private volatile List<PeerEurekaNode> peerEurekaNodes = Collections.emptyList();
        private volatile Set<String> peerEurekaNodeUrls = Collections.emptySet();

    还有三个重要的方法:

    updatePeerEurekaNodes 方法

        protected void updatePeerEurekaNodes(List<String> newPeerUrls) {
            if (newPeerUrls.isEmpty()) {
                logger.warn("The replica size seems to be empty. Check the route 53 DNS Registry");
            } else {
                Set<String> toShutdown = new HashSet(this.peerEurekaNodeUrls);
                toShutdown.removeAll(newPeerUrls);
                Set<String> toAdd = new HashSet(newPeerUrls);
                toAdd.removeAll(this.peerEurekaNodeUrls);
                if (!toShutdown.isEmpty() || !toAdd.isEmpty()) {
                    List<PeerEurekaNode> newNodeList = new ArrayList(this.peerEurekaNodes);
                    if (!toShutdown.isEmpty()) {
                        logger.info("Removing no longer available peer nodes {}", toShutdown);
                        int i = 0;
    
                        while(i < newNodeList.size()) {
                            PeerEurekaNode eurekaNode = (PeerEurekaNode)newNodeList.get(i);
                            if (toShutdown.contains(eurekaNode.getServiceUrl())) {
                                newNodeList.remove(i);
                                eurekaNode.shutDown();
                            } else {
                                ++i;
                            }
                        }
                    }
    
                    if (!toAdd.isEmpty()) {
                        logger.info("Adding new peer nodes {}", toAdd);
                        Iterator var7 = toAdd.iterator();
    
                        while(var7.hasNext()) {
                            String peerUrl = (String)var7.next();
                            newNodeList.add(this.createPeerEurekaNode(peerUrl));
                        }
                    }
    
                    this.peerEurekaNodes = newNodeList;
                    this.peerEurekaNodeUrls = new HashSet(newPeerUrls);
                }
            }
        }

    start 方法:

        public void start() {
            this.taskExecutor = Executors.newSingleThreadScheduledExecutor(new ThreadFactory() {
                public Thread newThread(Runnable r) {
                    Thread thread = new Thread(r, "Eureka-PeerNodesUpdater");
                    thread.setDaemon(true);
                    return thread;
                }
            });
    
            try {
                this.updatePeerEurekaNodes(this.resolvePeerUrls());
                Runnable peersUpdateTask = new Runnable() {
                    public void run() {
                        try {
                            PeerEurekaNodes.this.updatePeerEurekaNodes(PeerEurekaNodes.this.resolvePeerUrls());
                        } catch (Throwable var2) {
                            PeerEurekaNodes.logger.error("Cannot update the replica Nodes", var2);
                        }
    
                    }
                };
                this.taskExecutor.scheduleWithFixedDelay(peersUpdateTask, (long)this.serverConfig.getPeerEurekaNodesUpdateIntervalMs(), (long)this.serverConfig.getPeerEurekaNodesUpdateIntervalMs(), TimeUnit.MILLISECONDS);
            } catch (Exception var3) {
                throw new IllegalStateException(var3);
            }
    
            Iterator var4 = this.peerEurekaNodes.iterator();
    
            while(var4.hasNext()) {
                PeerEurekaNode node = (PeerEurekaNode)var4.next();
                logger.info("Replica node URL:  {}", node.getServiceUrl());
            }
    
        }

    方法内部开启了一个定时任务更新集群节点信息,默认的更新周期为600s,也就是10分钟。

    com.netflix.eureka.cluster.PeerEurekaNodes#resolvePeerUrls 方法从EurekaClientConfig 配置中解析集群其他节点的url

        protected List<String> resolvePeerUrls() {
            InstanceInfo myInfo = this.applicationInfoManager.getInfo();
            String zone = InstanceInfo.getZone(this.clientConfig.getAvailabilityZones(this.clientConfig.getRegion()), myInfo);
            List<String> replicaUrls = EndpointUtils.getDiscoveryServiceUrls(this.clientConfig, zone, new InstanceInfoBasedUrlRandomizer(myInfo));
            int idx = 0;
    
            while(idx < replicaUrls.size()) {
                if (this.isThisMyUrl((String)replicaUrls.get(idx))) {
                    replicaUrls.remove(idx);
                } else {
                    ++idx;
                }
            }
    
            return replicaUrls;
        }

    (4) 接下来注入了一个EurekaServerContext 上下文环境

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

    如下类:

    @Singleton
    public class DefaultEurekaServerContext implements EurekaServerContext {
        private static final Logger logger = LoggerFactory.getLogger(DefaultEurekaServerContext.class);
        private final EurekaServerConfig serverConfig;
        private final ServerCodecs serverCodecs;
        private final PeerAwareInstanceRegistry registry;
        private final PeerEurekaNodes peerEurekaNodes;
        private final ApplicationInfoManager applicationInfoManager;
    
        @Inject
        public DefaultEurekaServerContext(EurekaServerConfig serverConfig, ServerCodecs serverCodecs, PeerAwareInstanceRegistry registry, PeerEurekaNodes peerEurekaNodes, ApplicationInfoManager applicationInfoManager) {
            this.serverConfig = serverConfig;
            this.serverCodecs = serverCodecs;
            this.registry = registry;
            this.peerEurekaNodes = peerEurekaNodes;
            this.applicationInfoManager = applicationInfoManager;
        }
    
        @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");
        }
    
        @PreDestroy
        public void shutdown() {
            logger.info("Shutting down ...");
            this.registry.shutdown();
            this.peerEurekaNodes.shutdown();
            logger.info("Shut down");
        }
    
        public EurekaServerConfig getServerConfig() {
            return this.serverConfig;
        }
    
        public PeerEurekaNodes getPeerEurekaNodes() {
            return this.peerEurekaNodes;
        }
    
        public ServerCodecs getServerCodecs() {
            return this.serverCodecs;
        }
    
        public PeerAwareInstanceRegistry getRegistry() {
            return this.registry;
        }
    
        public ApplicationInfoManager getApplicationInfoManager() {
            return this.applicationInfoManager;
        }
    }

      @PostConstruct 初始化之前主要的操作:

    1》用this.peerEurekaNodes.start(); 开启了上面更对等节点刷新的任务;

    2》this.registry.init(this.peerEurekaNodes); 调用注册器的初始化方法,主要如下:

    com.netflix.eureka.registry.PeerAwareInstanceRegistryImpl#init:

        @Inject
        public PeerAwareInstanceRegistryImpl(EurekaServerConfig serverConfig, EurekaClientConfig clientConfig, ServerCodecs serverCodecs, EurekaClient eurekaClient) {
            super(serverConfig, clientConfig, serverCodecs);
            this.eurekaClient = eurekaClient;
            this.numberOfReplicationsLastMin = new MeasuredRate(60000L);
            this.instanceStatusOverrideRule = new FirstMatchWinsCompositeRule(new InstanceStatusOverrideRule[]{new DownOrStartingRule(), new OverrideExistsRule(this.overriddenInstanceStatusMap), new LeaseExistsRule()});
        }
    
        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);
            }
    
        }

    com.netflix.eureka.util.MeasuredRate#start 方法(启动了一个定时器, 每隔60就将currentBucket 清零)

        public synchronized void start() {
            if (!this.isActive) {
                this.timer.schedule(new TimerTask() {
                    public void run() {
                        try {
                            MeasuredRate.this.lastBucket.set(MeasuredRate.this.currentBucket.getAndSet(0L));
                        } catch (Throwable var2) {
                            MeasuredRate.logger.error("Cannot reset the Measured Rate", var2);
                        }
    
                    }
                }, this.sampleInterval, this.sampleInterval);
                this.isActive = true;
            }
    
        

    com.netflix.eureka.registry.PeerAwareInstanceRegistryImpl#scheduleRenewalThresholdUpdateTask:

        private void scheduleRenewalThresholdUpdateTask() {
            this.timer.schedule(new TimerTask() {
                public void run() {
                    PeerAwareInstanceRegistryImpl.this.updateRenewalThreshold();
                }
            }, (long)this.serverConfig.getRenewalThresholdUpdateIntervalMs(), (long)this.serverConfig.getRenewalThresholdUpdateIntervalMs());
        }

      this.serverConfig.getRenewalThresholdUpdateIntervalMs()  默认是900000, 也就是 15分钟。 (这个是更新与清除任务有关的阈值参数)

    com.netflix.eureka.registry.PeerAwareInstanceRegistryImpl#updateRenewalThreshold:

        private void updateRenewalThreshold() {
            try {
                Applications apps = eurekaClient.getApplications();
                int count = 0;
                for (Application app : apps.getRegisteredApplications()) {
                    for (InstanceInfo instance : app.getInstances()) {
                        if (this.isRegisterable(instance)) {
                            ++count;
                        }
                    }
                }
                synchronized (lock) {
                    // Update threshold only if the threshold is greater than the
                    // current expected threshold or if self preservation is disabled.
                    if ((count) > (serverConfig.getRenewalPercentThreshold() * expectedNumberOfClientsSendingRenews)
                            || (!this.isSelfPreservationModeEnabled())) {
                        this.expectedNumberOfClientsSendingRenews = count;
                        updateRenewsPerMinThreshold();
                    }
                }
                logger.info("Current renewal threshold is : {}", numberOfRenewsPerMinThreshold);
            } catch (Throwable e) {
                logger.error("Cannot update renewal threshold", e);
            }
        }

    com.netflix.eureka.registry.AbstractInstanceRegistry#updateRenewsPerMinThreshold:

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

    这里涉及到Eureka自我保护模式的三个参数:

        private final MeasuredRate renewsLastMin;    // 统计每分钟的心跳包
        protected volatile int numberOfRenewsPerMinThreshold;    // 每分钟 client 应该续期的最小次数
        protected volatile int expectedNumberOfClientsSendingRenews;    // 期望收到心跳的服务数量

    expectedNumberOfClientsSendingRenews  会在服务注册时加1

    (5) 接下来注入一些与Jersey相关的信息,目的是为了对外提供一些rest接口比如:注册、心跳、获取服务列表等接口.ServletContainer 既是一个filter, 又是一个Servlet。 这个是Jersey 暴露的一个类。

        /**
         * Register the Jersey filter.
         * @param eurekaJerseyApp an {@link Application} for the filter to be registered
         * @return a jersey {@link FilterRegistrationBean}
         */
        @Bean
        public FilterRegistrationBean<?> jerseyFilterRegistration(
                javax.ws.rs.core.Application eurekaJerseyApp) {
            FilterRegistrationBean<Filter> bean = new FilterRegistrationBean<Filter>();
            bean.setFilter(new ServletContainer(eurekaJerseyApp));
            bean.setOrder(Ordered.LOWEST_PRECEDENCE);
            bean.setUrlPatterns(
                    Collections.singletonList(EurekaConstants.DEFAULT_PREFIX + "/*"));
    
            return bean;
        }
    
        /**
         * Construct a Jersey {@link javax.ws.rs.core.Application} with all the resources
         * required by the Eureka server.
         * @param environment an {@link Environment} instance to retrieve classpath resources
         * @param resourceLoader a {@link ResourceLoader} instance to get classloader from
         * @return created {@link Application} object
         */
        @Bean
        public javax.ws.rs.core.Application jerseyApplication(Environment environment,
                ResourceLoader resourceLoader) {
    
            ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(
                    false, environment);
    
            // Filter to include only classes that have a particular annotation.
            //
            provider.addIncludeFilter(new AnnotationTypeFilter(Path.class));
            provider.addIncludeFilter(new AnnotationTypeFilter(Provider.class));
    
            // Find classes in Eureka packages (or subpackages)
            //
            Set<Class<?>> classes = new HashSet<>();
            for (String basePackage : EUREKA_PACKAGES) {
                Set<BeanDefinition> beans = provider.findCandidateComponents(basePackage);
                for (BeanDefinition bd : beans) {
                    Class<?> cls = ClassUtils.resolveClassName(bd.getBeanClassName(),
                            resourceLoader.getClassLoader());
                    classes.add(cls);
                }
            }
    
            // Construct the Jersey ResourceConfig
            Map<String, Object> propsAndFeatures = new HashMap<>();
            propsAndFeatures.put(
                    // Skip static content used by the webapp
                    ServletContainer.PROPERTY_WEB_PAGE_CONTENT_REGEX,
                    EurekaConstants.DEFAULT_PREFIX + "/(fonts|images|css|js)/.*");
    
            DefaultResourceConfig rc = new DefaultResourceConfig(classes);
            rc.setPropertiesAndFeatures(propsAndFeatures);
    
            return rc;
        }

        jerseyApplication 方法是注入Jersey的Resource, 也就是接收请求的类。查找com.netflix.discovery包和com.netflix.eureka包下带 Path和Provider 注解的类,然后注册到Jersey 中可以使用。参考类:com.netflix.eureka.resources.ApplicationsResource, 这些也就是真正接收客户端请求进行工作的类。 是Jersey 的resource 类。

      比如客户端拿取应用的类com.netflix.eureka.resources.ApplicationsResource#getContainers 和  com.netflix.eureka.resources.ApplicationResource#addInstance 接收客户端服务注册的方法。

    3. EurekaServerAutoConfiguration引入了EurekaServerInitializerConfiguration 配置类,我们查看该配置类的作用

    源码如下:

     1 @Configuration(proxyBeanMethods = false)
     2 public class EurekaServerInitializerConfiguration
     3         implements ServletContextAware, SmartLifecycle, Ordered {
     4 
     5     private static final Log log = LogFactory
     6             .getLog(EurekaServerInitializerConfiguration.class);
     7 
     8     @Autowired
     9     private EurekaServerConfig eurekaServerConfig;
    10 
    11     private ServletContext servletContext;
    12 
    13     @Autowired
    14     private ApplicationContext applicationContext;
    15 
    16     @Autowired
    17     private EurekaServerBootstrap eurekaServerBootstrap;
    18 
    19     private boolean running;
    20 
    21     private int order = 1;
    22 
    23     @Override
    24     public void setServletContext(ServletContext servletContext) {
    25         this.servletContext = servletContext;
    26     }
    27 
    28     @Override
    29     public void start() {
    30         new Thread(() -> {
    31             try {
    32                 // TODO: is this class even needed now?
    33                 eurekaServerBootstrap.contextInitialized(
    34                         EurekaServerInitializerConfiguration.this.servletContext);
    35                 log.info("Started Eureka Server");
    36 
    37                 publish(new EurekaRegistryAvailableEvent(getEurekaServerConfig()));
    38                 EurekaServerInitializerConfiguration.this.running = true;
    39                 publish(new EurekaServerStartedEvent(getEurekaServerConfig()));
    40             }
    41             catch (Exception ex) {
    42                 // Help!
    43                 log.error("Could not initialize Eureka servlet context", ex);
    44             }
    45         }).start();
    46     }
    47 
    48     private EurekaServerConfig getEurekaServerConfig() {
    49         return this.eurekaServerConfig;
    50     }
    51 
    52     private void publish(ApplicationEvent event) {
    53         this.applicationContext.publishEvent(event);
    54     }
    55 
    56     @Override
    57     public void stop() {
    58         this.running = false;
    59         eurekaServerBootstrap.contextDestroyed(this.servletContext);
    60     }
    61 
    62     @Override
    63     public boolean isRunning() {
    64         return this.running;
    65     }
    66 
    67     @Override
    68     public int getPhase() {
    69         return 0;
    70     }
    71 
    72     @Override
    73     public boolean isAutoStartup() {
    74         return true;
    75     }
    76 
    77     @Override
    78     public void stop(Runnable callback) {
    79         callback.run();
    80     }
    81 
    82     @Override
    83     public int getOrder() {
    84         return this.order;
    85     }
    86 
    87 }

      org.springframework.context.Lifecycle 接口在spring容器加载和初始化完毕执行一些操作, 其执行过程是在IoC最后一个环节:org.springframework.context.support.AbstractApplicationContext#refresh 方法里面的finishRefresh();方法

    可以看到其start 方法里面主要做了两件事:

    1. eurekaServerBootstrap 上下文初始化

    eurekaServerBootstrap.contextInitialized(EurekaServerInitializerConfiguration.this.servletContext);

    接着调用org.springframework.cloud.netflix.eureka.server.EurekaServerBootstrap#contextInitialized

        public void contextInitialized(ServletContext context) {
            try {
                initEurekaEnvironment();
                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);
            }
        }

    又做了两件事情:

    (1) 初始化环境信息

    (2) 初始化EurekaServer 上下文

     1     protected void initEurekaServerContext() throws Exception {
     2         // For backward compatibility
     3         JsonXStream.getInstance().registerConverter(new V1AwareInstanceInfoConverter(),
     4                 XStream.PRIORITY_VERY_HIGH);
     5         XmlXStream.getInstance().registerConverter(new V1AwareInstanceInfoConverter(),
     6                 XStream.PRIORITY_VERY_HIGH);
     7 
     8         if (isAws(this.applicationInfoManager.getInfo())) {
     9             this.awsBinder = new AwsBinderDelegate(this.eurekaServerConfig,
    10                     this.eurekaClientConfig, this.registry, this.applicationInfoManager);
    11             this.awsBinder.start();
    12         }
    13 
    14         EurekaServerContextHolder.initialize(this.serverContext);
    15 
    16         log.info("Initialized server context");
    17 
    18         // Copy registry from neighboring eureka node
    19         int registryCount = this.registry.syncUp();
    20         this.registry.openForTraffic(this.applicationInfoManager, registryCount);
    21 
    22         // Register all monitoring statistics.
    23         EurekaMonitors.registerAllStats();
    24     }

    3-6 行注册了两个转换器

    14 行EurekaServerContextHolder 维护一个在非DI环境可以使用EurekaServerContext 变量

    19行从集群中其他节点将已经注册的服务拿过来:com.netflix.eureka.registry.PeerAwareInstanceRegistryImpl#syncUp

     1     @Override
     2     public int syncUp() {
     3         // Copy entire entry from neighboring DS node
     4         int count = 0;
     5 
     6         for (int i = 0; ((i < serverConfig.getRegistrySyncRetries()) && (count == 0)); i++) {
     7             if (i > 0) {
     8                 try {
     9                     Thread.sleep(serverConfig.getRegistrySyncRetryWaitMs());
    10                 } catch (InterruptedException e) {
    11                     logger.warn("Interrupted during registry transfer..");
    12                     break;
    13                 }
    14             }
    15             Applications apps = eurekaClient.getApplications();
    16             for (Application app : apps.getRegisteredApplications()) {
    17                 for (InstanceInfo instance : app.getInstances()) {
    18                     try {
    19                         if (isRegisterable(instance)) {
    20                             register(instance, instance.getLeaseInfo().getDurationInSecs(), true);
    21                             count++;
    22                         }
    23                     } catch (Throwable t) {
    24                         logger.error("During DS init copy", t);
    25                     }
    26                 }
    27             }
    28         }
    29         return count;
    30     }

    com.netflix.eureka.registry.PeerAwareInstanceRegistryImpl#openForTraffic(1、把服务状态设置为UP;2、调用父类的postInit方法)

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

    最后调用父类的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());
        }

    (1)开启续约定时任务;

    续约定时任务是每60s清零,这个用于记录1分钟内续约的次数

    public class MeasuredRate {
        private static final Logger logger = LoggerFactory.getLogger(MeasuredRate.class);
        private final AtomicLong lastBucket = new AtomicLong(0);
        private final AtomicLong currentBucket = new AtomicLong(0);
    
        private final long sampleInterval;
        private final Timer timer;
    
        private volatile boolean isActive;
    
        /**
         * @param sampleInterval in milliseconds
         */
        public MeasuredRate(long sampleInterval) {
            this.sampleInterval = sampleInterval;
            this.timer = new Timer("Eureka-MeasureRateTimer", true);
            this.isActive = false;
        }
    
        public synchronized void start() {
            if (!isActive) {
                timer.schedule(new TimerTask() {
    
                    @Override
                    public void run() {
                        try {
                            // Zero out the current bucket.
                            lastBucket.set(currentBucket.getAndSet(0));
                        } catch (Throwable e) {
                            logger.error("Cannot reset the Measured Rate", e);
                        }
                    }
                }, sampleInterval, sampleInterval);
    
                isActive = true;
            }
        }
    
        public synchronized void stop() {
            if (isActive) {
                timer.cancel();
                isActive = false;
            }
        }
    
        /**
         * Returns the count in the last sample interval.
         */
        public long getCount() {
            return lastBucket.get();
        }
    
        /**
         * Increments the count in the current sample interval.
         */
        public void increment() {
            currentBucket.incrementAndGet();
        }
    }

    (2)开启过期剔除定时任务

      serverConfig.getEvictionIntervalTimerInMs() 默认是60 000, 也就是60s为周期定时清除过期的任务。

        /* 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.
             */
            long getCompensationTimeMs() {
                long currNanos = getCurrentTimeNano();
                long lastNanos = lastExecutionNanosRef.getAndSet(currNanos);
                if (lastNanos == 0l) {
                    return 0l;
                }
    
                long elapsedMs = TimeUnit.NANOSECONDS.toMillis(currNanos - lastNanos);
                long compensationTime = elapsedMs - serverConfig.getEvictionIntervalTimerInMs();
                return compensationTime <= 0l ? 0l : compensationTime;
            }
    
            long getCurrentTimeNano() {  // for testing
                return System.nanoTime();
            }
    
        }

      com.netflix.eureka.registry.AbstractInstanceRegistry#evict(long)如下:

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

    -1》com.netflix.eureka.registry.PeerAwareInstanceRegistryImpl#isLeaseExpirationEnabled 如下: 判断是否清除过期服务

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

       这里的判断是否清除过期服务和Eureka自我保护模式有关:

    自我保护模式关闭: 会清除

    自我保护模式开启 并且 过去一分钟收到的心跳数量大于numberOfRenewsPerMinThreshold(每分钟期望收到的最小值,也就是临界值) 会清除

    自我保护模式开启 并且 过去一分钟收到的心跳数量小于等于numberOfRenewsPerMinThreshold(每分钟期望收到的最小值,也就是临界值) 不会清除,相当进入自我保护模式。

    -2》  numberOfRenewsPerMinThreshold 临界值计算方式为:com.netflix.eureka.registry.AbstractInstanceRegistry#updateRenewsPerMinThreshold(15分钟更新一次numberOfRenewsPerMinThreshold 临界值。(在注册和下线的时候也都会重新-计算))

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

    expectedNumberOfClientsSendingRenews 是期望收到客户端心跳的服务数量,这个值在注册的时候加一, cancel下线的时候减一。openForTraffic 方法启动如果是0会将该值默认改为1。

    (60.0 / serverConfig.getExpectedClientRenewalIntervalSeconds()) 是一分钟收到的数量,比如默认是30s一次,则一分钟期望收到的是两次

    renewalPercentThreshold 是一个比例,默认是0.85。

    注意:每个15分钟修改该值是在:com.netflix.eureka.registry.PeerAwareInstanceRegistryImpl#scheduleRenewalThresholdUpdateTask

        private void scheduleRenewalThresholdUpdateTask() {
            timer.schedule(new TimerTask() {
                               @Override
                               public void run() {
                                   updateRenewalThreshold();
                               }
                           }, serverConfig.getRenewalThresholdUpdateIntervalMs(),
                    serverConfig.getRenewalThresholdUpdateIntervalMs());
        }

     com.netflix.eureka.registry.PeerAwareInstanceRegistryImpl#updateRenewalThreshold

        /**
         * Updates the <em>renewal threshold</em> based on the current number of
         * renewals. The threshold is a percentage as specified in
         * {@link EurekaServerConfig#getRenewalPercentThreshold()} of renewals
         * received per minute {@link #getNumOfRenewsInLastMin()}.
         */
        private void updateRenewalThreshold() {
            try {
                Applications apps = eurekaClient.getApplications();
                int count = 0;
                for (Application app : apps.getRegisteredApplications()) {
                    for (InstanceInfo instance : app.getInstances()) {
                        if (this.isRegisterable(instance)) {
                            ++count;
                        }
                    }
                }
                synchronized (lock) {
                    // Update threshold only if the threshold is greater than the
                    // current expected threshold or if self preservation is disabled.
                    if ((count) > (serverConfig.getRenewalPercentThreshold() * expectedNumberOfClientsSendingRenews)
                            || (!this.isSelfPreservationModeEnabled())) {
                        this.expectedNumberOfClientsSendingRenews = count;
                        updateRenewsPerMinThreshold();
                    }
                }
                logger.info("Current renewal threshold is : {}", numberOfRenewsPerMinThreshold);
            } catch (Throwable e) {
                logger.error("Cannot update renewal threshold", e);
            }
        }

      注释写的非常明白: 更新阈值的前提条件是自我保护模式关闭或者 当前注册的服务数量>期望注册*0.85 。也就是自我保护模式开启的状态下如果有低于85% 的服务 不正常,不会更新阈值。

    -3》接下来是清理过期的任务:

    判断服务是超期服务的条件:com.netflix.eureka.lease.Lease#isExpired(long)

        public boolean isExpired(long additionalLeaseMs) {
            return (evictionTimestamp > 0 || System.currentTimeMillis() > (lastUpdateTimestamp + duration + additionalLeaseMs));
        }

    duration默认是90s。 也就是说如果90s之内没有收到服务的心跳算是服务过期。 接下来调用 internalCancel(appName, id, false); 方法清除过期的服务。

    2. 发布一些事件

    总结:

    会以定时任务每60s 执行一次清除过期服务的方法

    (1)过期服务的条件: 90s之内没有收到服务的心跳

    (2) 是否清除过期服务的条件:

    自我保护模式关闭:清除

    自我保护模式开启:

      上一分钟心跳数量小于每分钟阈值,不会清除

      上一分钟心跳数量大于等于每分钟阈值,会清除

    (3) 每分钟阈值的计算方法: 这个阈值会以15分钟为期限进行修改(如果自我保护模式开始且当前在线的服务 >= 期望在线服务 * 0.85 会进行修改,也就是低于85% 服务在线不会修改,相当于自我保护模式生效)

      比例(0.85) * 期望收到心跳的服务 * 每分钟心跳次数

    4. EurekaController 面板Controller 查看

    1. 先查看一个面板,界面如下:

    1. Controller 代码如下:

      1 @Controller
      2 @RequestMapping("${eureka.dashboard.path:/}")
      3 public class EurekaController {
      4 
      5     @Value("${eureka.dashboard.path:/}")
      6     private String dashboardPath = "";
      7 
      8     private ApplicationInfoManager applicationInfoManager;
      9 
     10     public EurekaController(ApplicationInfoManager applicationInfoManager) {
     11         this.applicationInfoManager = applicationInfoManager;
     12     }
     13 
     14     @RequestMapping(method = RequestMethod.GET)
     15     public String status(HttpServletRequest request, Map<String, Object> model) {
     16         populateBase(request, model);
     17         populateApps(model);
     18         StatusInfo statusInfo;
     19         try {
     20             statusInfo = new StatusResource().getStatusInfo();
     21         }
     22         catch (Exception e) {
     23             statusInfo = StatusInfo.Builder.newBuilder().isHealthy(false).build();
     24         }
     25         model.put("statusInfo", statusInfo);
     26         populateInstanceInfo(model, statusInfo);
     27         filterReplicas(model, statusInfo);
     28         return "eureka/status";
     29     }
     30 
     31     @RequestMapping(value = "/lastn", method = RequestMethod.GET)
     32     public String lastn(HttpServletRequest request, Map<String, Object> model) {
     33         populateBase(request, model);
     34         PeerAwareInstanceRegistryImpl registry = (PeerAwareInstanceRegistryImpl) getRegistry();
     35         ArrayList<Map<String, Object>> lastNCanceled = new ArrayList<>();
     36         List<Pair<Long, String>> list = registry.getLastNCanceledInstances();
     37         for (Pair<Long, String> entry : list) {
     38             lastNCanceled.add(registeredInstance(entry.second(), entry.first()));
     39         }
     40         model.put("lastNCanceled", lastNCanceled);
     41         list = registry.getLastNRegisteredInstances();
     42         ArrayList<Map<String, Object>> lastNRegistered = new ArrayList<>();
     43         for (Pair<Long, String> entry : list) {
     44             lastNRegistered.add(registeredInstance(entry.second(), entry.first()));
     45         }
     46         model.put("lastNRegistered", lastNRegistered);
     47         return "eureka/lastn";
     48     }
     49 
     50     private Map<String, Object> registeredInstance(String id, long date) {
     51         HashMap<String, Object> map = new HashMap<>();
     52         map.put("id", id);
     53         map.put("date", new Date(date));
     54         return map;
     55     }
     56 
     57     protected void populateBase(HttpServletRequest request, Map<String, Object> model) {
     58         model.put("time", new Date());
     59         model.put("basePath", "/");
     60         model.put("dashboardPath",
     61                 this.dashboardPath.equals("/") ? "" : this.dashboardPath);
     62         populateHeader(model);
     63         populateNavbar(request, model);
     64     }
     65 
     66     private void populateHeader(Map<String, Object> model) {
     67         model.put("currentTime", StatusResource.getCurrentTimeAsString());
     68         model.put("upTime", StatusInfo.getUpTime());
     69         model.put("environment",
     70                 ConfigurationManager.getDeploymentContext().getDeploymentEnvironment());
     71         model.put("datacenter",
     72                 ConfigurationManager.getDeploymentContext().getDeploymentDatacenter());
     73         PeerAwareInstanceRegistry registry = getRegistry();
     74         model.put("registry", registry);
     75         model.put("isBelowRenewThresold", registry.isBelowRenewThresold() == 1);
     76         DataCenterInfo info = applicationInfoManager.getInfo().getDataCenterInfo();
     77         if (info.getName() == DataCenterInfo.Name.Amazon) {
     78             AmazonInfo amazonInfo = (AmazonInfo) info;
     79             model.put("amazonInfo", amazonInfo);
     80             model.put("amiId", amazonInfo.get(AmazonInfo.MetaDataKey.amiId));
     81             model.put("availabilityZone",
     82                     amazonInfo.get(AmazonInfo.MetaDataKey.availabilityZone));
     83             model.put("instanceId", amazonInfo.get(AmazonInfo.MetaDataKey.instanceId));
     84         }
     85     }
     86 
     87     private PeerAwareInstanceRegistry getRegistry() {
     88         return getServerContext().getRegistry();
     89     }
     90 
     91     private EurekaServerContext getServerContext() {
     92         return EurekaServerContextHolder.getInstance().getServerContext();
     93     }
     94 
     95     private void populateNavbar(HttpServletRequest request, Map<String, Object> model) {
     96         Map<String, String> replicas = new LinkedHashMap<>();
     97         List<PeerEurekaNode> list = getServerContext().getPeerEurekaNodes()
     98                 .getPeerNodesView();
     99         for (PeerEurekaNode node : list) {
    100             try {
    101                 URI uri = new URI(node.getServiceUrl());
    102                 String href = scrubBasicAuth(node.getServiceUrl());
    103                 replicas.put(uri.getHost(), href);
    104             }
    105             catch (Exception ex) {
    106                 // ignore?
    107             }
    108         }
    109         model.put("replicas", replicas.entrySet());
    110     }
    111 
    112     private void populateApps(Map<String, Object> model) {
    113         List<Application> sortedApplications = getRegistry().getSortedApplications();
    114         ArrayList<Map<String, Object>> apps = new ArrayList<>();
    115         for (Application app : sortedApplications) {
    116             LinkedHashMap<String, Object> appData = new LinkedHashMap<>();
    117             apps.add(appData);
    118             appData.put("name", app.getName());
    119             Map<String, Integer> amiCounts = new HashMap<>();
    120             Map<InstanceInfo.InstanceStatus, List<Pair<String, String>>> instancesByStatus = new HashMap<>();
    121             Map<String, Integer> zoneCounts = new HashMap<>();
    122             for (InstanceInfo info : app.getInstances()) {
    123                 String id = info.getId();
    124                 String url = info.getStatusPageUrl();
    125                 InstanceInfo.InstanceStatus status = info.getStatus();
    126                 String ami = "n/a";
    127                 String zone = "";
    128                 if (info.getDataCenterInfo().getName() == DataCenterInfo.Name.Amazon) {
    129                     AmazonInfo dcInfo = (AmazonInfo) info.getDataCenterInfo();
    130                     ami = dcInfo.get(AmazonInfo.MetaDataKey.amiId);
    131                     zone = dcInfo.get(AmazonInfo.MetaDataKey.availabilityZone);
    132                 }
    133                 Integer count = amiCounts.get(ami);
    134                 if (count != null) {
    135                     amiCounts.put(ami, count + 1);
    136                 }
    137                 else {
    138                     amiCounts.put(ami, 1);
    139                 }
    140                 count = zoneCounts.get(zone);
    141                 if (count != null) {
    142                     zoneCounts.put(zone, count + 1);
    143                 }
    144                 else {
    145                     zoneCounts.put(zone, 1);
    146                 }
    147                 List<Pair<String, String>> list = instancesByStatus
    148                         .computeIfAbsent(status, k -> new ArrayList<>());
    149                 list.add(new Pair<>(id, url));
    150             }
    151             appData.put("amiCounts", amiCounts.entrySet());
    152             appData.put("zoneCounts", zoneCounts.entrySet());
    153             ArrayList<Map<String, Object>> instanceInfos = new ArrayList<>();
    154             appData.put("instanceInfos", instanceInfos);
    155             for (Map.Entry<InstanceInfo.InstanceStatus, List<Pair<String, String>>> entry : instancesByStatus
    156                     .entrySet()) {
    157                 List<Pair<String, String>> value = entry.getValue();
    158                 InstanceInfo.InstanceStatus status = entry.getKey();
    159                 LinkedHashMap<String, Object> instanceData = new LinkedHashMap<>();
    160                 instanceInfos.add(instanceData);
    161                 instanceData.put("status", entry.getKey());
    162                 ArrayList<Map<String, Object>> instances = new ArrayList<>();
    163                 instanceData.put("instances", instances);
    164                 instanceData.put("isNotUp", status != InstanceInfo.InstanceStatus.UP);
    165 
    166                 // TODO
    167 
    168                 /*
    169                  * if(status != InstanceInfo.InstanceStatus.UP){
    170                  * buf.append("<font color=red size=+1><b>"); }
    171                  * buf.append("<b>").append(status
    172                  * .name()).append("</b> (").append(value.size()).append(") - ");
    173                  * if(status != InstanceInfo.InstanceStatus.UP){
    174                  * buf.append("</font></b>"); }
    175                  */
    176 
    177                 for (Pair<String, String> p : value) {
    178                     LinkedHashMap<String, Object> instance = new LinkedHashMap<>();
    179                     instances.add(instance);
    180                     instance.put("id", p.first());
    181                     String url = p.second();
    182                     instance.put("url", url);
    183                     boolean isHref = url != null && url.startsWith("http");
    184                     instance.put("isHref", isHref);
    185                     /*
    186                      * String id = p.first(); String url = p.second(); if(url != null &&
    187                      * url.startsWith("http")){
    188                      * buf.append("<a href="").append(url).append("">"); }else { url =
    189                      * null; } buf.append(id); if(url != null){ buf.append("</a>"); }
    190                      * buf.append(", ");
    191                      */
    192                 }
    193             }
    194             // out.println("<td>" + buf.toString() + "</td></tr>");
    195         }
    196         model.put("apps", apps);
    197     }
    198 
    199     private void populateInstanceInfo(Map<String, Object> model, StatusInfo statusInfo) {
    200         InstanceInfo instanceInfo = statusInfo.getInstanceInfo();
    201         Map<String, String> instanceMap = new HashMap<>();
    202         instanceMap.put("ipAddr", instanceInfo.getIPAddr());
    203         instanceMap.put("status", instanceInfo.getStatus().toString());
    204         if (instanceInfo.getDataCenterInfo().getName() == DataCenterInfo.Name.Amazon) {
    205             AmazonInfo info = (AmazonInfo) instanceInfo.getDataCenterInfo();
    206             instanceMap.put("availability-zone",
    207                     info.get(AmazonInfo.MetaDataKey.availabilityZone));
    208             instanceMap.put("public-ipv4", info.get(AmazonInfo.MetaDataKey.publicIpv4));
    209             instanceMap.put("instance-id", info.get(AmazonInfo.MetaDataKey.instanceId));
    210             instanceMap.put("public-hostname",
    211                     info.get(AmazonInfo.MetaDataKey.publicHostname));
    212             instanceMap.put("ami-id", info.get(AmazonInfo.MetaDataKey.amiId));
    213             instanceMap.put("instance-type",
    214                     info.get(AmazonInfo.MetaDataKey.instanceType));
    215         }
    216         model.put("instanceInfo", instanceMap);
    217     }
    218 
    219     protected void filterReplicas(Map<String, Object> model, StatusInfo statusInfo) {
    220         Map<String, String> applicationStats = statusInfo.getApplicationStats();
    221         if (applicationStats.get("registered-replicas").contains("@")) {
    222             applicationStats.put("registered-replicas",
    223                     scrubBasicAuth(applicationStats.get("registered-replicas")));
    224         }
    225         if (applicationStats.get("unavailable-replicas").contains("@")) {
    226             applicationStats.put("unavailable-replicas",
    227                     scrubBasicAuth(applicationStats.get("unavailable-replicas")));
    228         }
    229         if (applicationStats.get("available-replicas").contains("@")) {
    230             applicationStats.put("available-replicas",
    231                     scrubBasicAuth(applicationStats.get("available-replicas")));
    232         }
    233         model.put("applicationStats", applicationStats);
    234     }
    235 
    236     private String scrubBasicAuth(String urlList) {
    237         String[] urls = urlList.split(",");
    238         StringBuilder filteredUrls = new StringBuilder();
    239         for (String u : urls) {
    240             if (u.contains("@")) {
    241                 filteredUrls.append(u, 0, u.indexOf("//") + 2)
    242                         .append(u.substring(u.indexOf("@") + 1)).append(",");
    243             }
    244             else {
    245                 filteredUrls.append(u).append(",");
    246             }
    247         }
    248         return filteredUrls.substring(0, filteredUrls.length() - 1);
    249     }
    250 
    251 }
    View Code

    2. 可以看到默认访问的地址是/, 默认访问的handler的方法是status。

    1.  后端代码逻辑

    这个方法里面会拼装这个界面锁需要的数据,具体的包括:

    populateBase 方法拼装一些基础属性

    populateHeader    拼装header头部所需要的属性,这里有一个重要的属性:PeerAwareInstanceRegistry

    populateNavbar    拼装导航栏需要的属性

    model.put("statusInfo", statusInfo);   拼接了状态信息

    populateApps  拼接了客户端注册的服务信息

    populateInstanceInfo    获取了当前服务信息,这里注意其状态有五种:

        public enum InstanceStatus {
            UP, // Ready to receive traffic
            DOWN, // Do not send traffic- healthcheck callback failed
            STARTING, // Just about starting- initializations to be done - do not
            // send traffic
            OUT_OF_SERVICE, // Intentionally shutdown for traffic
            UNKNOWN;
    
            public static InstanceStatus toEnum(String s) {
                if (s != null) {
                    try {
                        return InstanceStatus.valueOf(s.toUpperCase());
                    } catch (IllegalArgumentException e) {
                        // ignore and fall through to unknown
                        logger.debug("illegal argument supplied to InstanceStatus.valueOf: {}, defaulting to {}", s, UNKNOWN);
                    }
                }
                return UNKNOWN;
            }
        }

    2. 界面取数据逻辑

    status.ftlh 如下:

      1 <#import "/spring.ftl" as spring />
      2 <!doctype html>
      3 <!--[if lt IE 7]>      <html class="no-js lt-ie9 lt-ie8 lt-ie7"> <![endif]-->
      4 <!--[if IE 7]>         <html class="no-js lt-ie9 lt-ie8"> <![endif]-->
      5 <!--[if IE 8]>         <html class="no-js lt-ie9"> <![endif]-->
      6 <!--[if gt IE 8]><!--> <html class="no-js"> <!--<![endif]-->
      7   <head>
      8     <base href="<@spring.url basePath/>">
      9     <meta charset="utf-8">
     10     <meta http-equiv="X-UA-Compatible" content="IE=edge">
     11     <title>Eureka</title>
     12     <meta name="description" content="">
     13     <meta name="viewport" content="width=device-width">
     14 
     15     <link rel="stylesheet" href="eureka/css/wro.css">
     16 
     17   </head>
     18 
     19   <body id="one">
     20     <#include "header.ftlh">
     21     <div class="container-fluid xd-container">
     22       <#include "navbar.ftlh">
     23       <h1>Instances currently registered with Eureka</h1>
     24       <table id='instances' class="table table-striped table-hover">
     25         <thead>
     26           <tr><th>Application</th><th>AMIs</th><th>Availability Zones</th><th>Status</th></tr>
     27         </thead>
     28         <tbody>
     29           <#if apps?has_content>
     30             <#list apps as app>
     31               <tr>
     32                 <td><b>${app.name}</b></td>
     33                 <td>
     34                   <#list app.amiCounts as amiCount>
     35                     <b>${amiCount.key}</b> (${amiCount.value})<#if amiCount_has_next>,</#if>
     36                   </#list>
     37                 </td>
     38                 <td>
     39                   <#list app.zoneCounts as zoneCount>
     40                     <b>${zoneCount.key}</b> (${zoneCount.value})<#if zoneCount_has_next>,</#if>
     41                   </#list>
     42                 </td>
     43                 <td>
     44                   <#list app.instanceInfos as instanceInfo>
     45                     <#if instanceInfo.isNotUp>
     46                       <font color=red size=+1><b>
     47                     </#if>
     48                     <b>${instanceInfo.status}</b> (${instanceInfo.instances?size}) -
     49                     <#if instanceInfo.isNotUp>
     50                       </b></font>
     51                     </#if>
     52                     <#list instanceInfo.instances as instance>
     53                       <#if instance.isHref>
     54                         <a href="${instance.url}" target="_blank">${instance.id}</a>
     55                       <#else>
     56                         ${instance.id}
     57                       </#if><#if instance_has_next>,</#if>
     58                     </#list>
     59                   </#list>
     60                 </td>
     61               </tr>
     62             </#list>
     63           <#else>
     64             <tr><td colspan="4">No instances available</td></tr>
     65           </#if>
     66 
     67         </tbody>
     68       </table>
     69 
     70       <h1>General Info</h1>
     71 
     72       <table id='generalInfo' class="table table-striped table-hover">
     73         <thead>
     74           <tr><th>Name</th><th>Value</th></tr>
     75         </thead>
     76         <tbody>
     77           <#list statusInfo.generalStats?keys as stat>
     78             <tr>
     79               <td>${stat}</td><td>${statusInfo.generalStats[stat]!""}</td>
     80             </tr>
     81           </#list>
     82           <#list statusInfo.applicationStats?keys as stat>
     83             <tr>
     84               <td>${stat}</td><td>${statusInfo.applicationStats[stat]!""}</td>
     85             </tr>
     86           </#list>
     87         </tbody>
     88       </table>
     89 
     90       <h1>Instance Info</h1>
     91 
     92       <table id='instanceInfo' class="table table-striped table-hover">
     93         <thead>
     94           <tr><th>Name</th><th>Value</th></tr>
     95         <thead>
     96         <tbody>
     97           <#list instanceInfo?keys as key>
     98             <tr>
     99               <td>${key}</td><td>${instanceInfo[key]!""}</td>
    100             </tr>
    101           </#list>
    102         </tbody>
    103       </table>
    104     </div>
    105     <script type="text/javascript" src="eureka/js/wro.js" ></script>
    106     <script type="text/javascript">
    107        $(document).ready(function() {
    108          $('table.stripeable tr:odd').addClass('odd');
    109          $('table.stripeable tr:even').addClass('even');
    110        });
    111     </script>
    112   </body>
    113 </html>
    View Code

    header.ftlh

     1 <#import "/spring.ftl" as spring />
     2 <nav class="navbar navbar-default" role="navigation">
     3   <div class="container">
     4     <div class="navbar-header">
     5       <a class="navbar-brand" href="<@spring.url dashboardPath/>"><span></span></a>
     6       <button type="button" class="navbar-toggle" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1">
     7         <span class="sr-only">Toggle navigation</span>
     8         <span class="icon-bar"></span>
     9         <span class="icon-bar"></span>
    10         <span class="icon-bar"></span>
    11       </button>
    12     </div>
    13     <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
    14       <ul class="nav navbar-nav navbar-right">
    15         <li>
    16           <a href="<@spring.url dashboardPath/>">Home</a>
    17         </li>
    18         <li>
    19           <a href="<@spring.url dashboardPath/>/lastn">Last 1000 since startup</a>
    20         </li>
    21       </ul>
    22     </div>
    23   </div>
    24 </nav>
    View Code

    navbar.ftlh:

     1 <h1>System Status</h1>
     2 <div class="row">
     3   <div class="col-md-6">
     4     <table id='instances' class="table table-condensed table-striped table-hover">
     5       <#if amazonInfo??>
     6         <tr>
     7           <td>EUREKA SERVER</td>
     8           <td>AMI: ${amiId!}</td>
     9         </tr>
    10         <tr>
    11           <td>Zone</td>
    12           <td>${availabilityZone!}</td>
    13         </tr>
    14         <tr>
    15           <td>instance-id</td>
    16           <td>${instanceId!}</td>
    17         </tr>
    18       </#if>
    19       <tr>
    20         <td>Environment</td>
    21         <td>${environment!}</td>
    22       </tr>
    23       <tr>
    24         <td>Data center</td>
    25         <td>${datacenter!}</td>
    26       </tr>
    27     </table>
    28   </div>
    29   <div class="col-md-6">
    30     <table id='instances' class="table table-condensed table-striped table-hover">
    31       <tr>
    32         <td>Current time</td>
    33         <td>${currentTime}</td>
    34       </tr>
    35       <tr>
    36         <td>Uptime</td>
    37         <td>${upTime}</td>
    38       </tr>
    39       <tr>
    40         <td>Lease expiration enabled</td>
    41         <td>${registry.leaseExpirationEnabled?c}</td>
    42       </tr>
    43       <tr>
    44         <td>Renews threshold</td>
    45         <td>${registry.numOfRenewsPerMinThreshold}</td>
    46       </tr>
    47       <tr>
    48         <td>Renews (last min)</td>
    49         <td>${registry.numOfRenewsInLastMin}</td>
    50       </tr>
    51     </table>
    52   </div>
    53 </div>
    54 
    55 <#if isBelowRenewThresold>
    56     <#if !registry.selfPreservationModeEnabled>
    57         <h4 id="uptime"><font size="+1" color="red"><b>RENEWALS ARE LESSER THAN THE THRESHOLD. THE SELF PRESERVATION MODE IS TURNED OFF. THIS MAY NOT PROTECT INSTANCE EXPIRY IN CASE OF NETWORK/OTHER PROBLEMS.</b></font></h4>
    58     <#else>
    59         <h4 id="uptime"><font size="+1" color="red"><b>EMERGENCY! EUREKA MAY BE INCORRECTLY CLAIMING INSTANCES ARE UP WHEN THEY'RE NOT. RENEWALS ARE LESSER THAN THRESHOLD AND HENCE THE INSTANCES ARE NOT BEING EXPIRED JUST TO BE SAFE.</b></font></h4>
    60     </#if>
    61 <#elseif !registry.selfPreservationModeEnabled>
    62     <h4 id="uptime"><font size="+1" color="red"><b>THE SELF PRESERVATION MODE IS TURNED OFF. THIS MAY NOT PROTECT INSTANCE EXPIRY IN CASE OF NETWORK/OTHER PROBLEMS.</b></font></h4>
    63 </#if>
    64 
    65 <h1>DS Replicas</h1>
    66 <ul class="list-group">
    67   <#list replicas as replica>
    68     <li class="list-group-item"><a href="${replica.value}">${replica.key}</a></li>
    69   </#list>
    70 </ul>
    View Code

    可以看到status静态资源文件引入了header和navbar资源。

    1. header 主要展示:(也就是头顶的信息, 并且提供了一个访问lastn 过去上线和注册的链接)

     2. navbar 展示如下:

     可以看到展示了一些默认的系统属性,然后右边展示了和清除过期服务相关的参数。

      Lease expiration enabled    值取的是registry.leaseExpirationEnabled, 也就是上面提到的能否清除过期服务的方法。 也就是方法com.netflix.eureka.registry.PeerAwareInstanceRegistryImpl#isLeaseExpirationEnabled。 其计算规则和自我保护模式是否开启以及上一分钟的心跳数量和心跳下限有关。

      Renews threshold 心跳阈值也就是每分钟允许的最小心跳值。

      Renews (last min) 是上一分钟收到的心跳数值。 可以看到数量小于阈值,所以在自我保护模式开启的前提下Lease expiration enabled 是false。

      下面展示的红色提示语是根据selfPreservationModeEnabled参数以及上面两个心跳数值有关。可以看到上面心跳低于阈值,且自我保护开启,因此应该展示:

       接下来遍历集群其他节点进行展示,对应后端获取集群其他节点的方法org.springframework.cloud.netflix.eureka.server.EurekaController#populateNavbar

    3. 接下来status 模板下半部分展示了集群中服务信息以及自身的服务状态信息

    5. Eureka 接收客户端请求

      在上面了解到接收请求工作的是Jersey 的Resource类,比如注册时候的Resource:com.netflix.eureka.resources.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
            if (isBlank(info.getId())) {
                return Response.status(400).entity("Missing instanceId").build();
            } else if (isBlank(info.getHostName())) {
                return Response.status(400).entity("Missing hostname").build();
            } else if (isBlank(info.getIPAddr())) {
                return Response.status(400).entity("Missing ip address").build();
            } else if (isBlank(info.getAppName())) {
                return Response.status(400).entity("Missing appName").build();
            } else if (!appName.equals(info.getAppName())) {
                return Response.status(400).entity("Mismatched appName, expecting " + appName + " but was " + info.getAppName()).build();
            } else if (info.getDataCenterInfo() == null) {
                return Response.status(400).entity("Missing dataCenterInfo").build();
            } else if (info.getDataCenterInfo().getName() == null) {
                return Response.status(400).entity("Missing dataCenterInfo Name").build();
            }
    
            // handle cases where clients may be registering with bad DataCenterInfo with missing data
            DataCenterInfo dataCenterInfo = info.getDataCenterInfo();
            if (dataCenterInfo instanceof UniqueIdentifier) {
                String dataCenterInfoId = ((UniqueIdentifier) dataCenterInfo).getId();
                if (isBlank(dataCenterInfoId)) {
                    boolean experimental = "true".equalsIgnoreCase(serverConfig.getExperimental("registration.validation.dataCenterInfoId"));
                    if (experimental) {
                        String entity = "DataCenterInfo of type " + dataCenterInfo.getClass() + " must contain a valid id";
                        return Response.status(400).entity(entity).build();
                    } else if (dataCenterInfo instanceof AmazonInfo) {
                        AmazonInfo amazonInfo = (AmazonInfo) dataCenterInfo;
                        String effectiveId = amazonInfo.get(AmazonInfo.MetaDataKey.instanceId);
                        if (effectiveId == null) {
                            amazonInfo.getMetadata().put(AmazonInfo.MetaDataKey.instanceId.getName(), info.getId());
                        }
                    } else {
                        logger.warn("Registering DataCenterInfo of type {} without an appropriate id", dataCenterInfo.getClass());
                    }
                }
            }
    
            registry.register(info, "true".equals(isReplication));
            return Response.status(204).build();  // 204 to be backwards compatible
        }

    1. org.springframework.cloud.netflix.eureka.server.InstanceRegistry#register(com.netflix.appinfo.InstanceInfo, boolean)

        @Override
        public void register(final InstanceInfo info, final boolean isReplication) {
            handleRegistration(info, resolveInstanceLeaseDuration(info), isReplication);
            super.register(info, isReplication);
        }

    2. com.netflix.eureka.registry.PeerAwareInstanceRegistryImpl#register

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

      调用父类注册逻辑,然后通知其他兄弟节点。

    3. com.netflix.eureka.registry.AbstractInstanceRegistry#register

      这里就是注册的逻辑,添加到注册缓存com.netflix.eureka.registry.AbstractInstanceRegistry#registry 中。

    6. EurekaServer 三级缓存 

    1. com.netflix.eureka.registry.AbstractInstanceRegistry#registry 三级写缓存(注册之后的存在这里)

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

    2. com.netflix.eureka.registry.ResponseCacheImpl#readWriteCacheMap 二级读写缓存,相当于是一级和二级的桥梁

    private final LoadingCache<Key, Value> readWriteCacheMap; 是guava 的一个工具类

    3. com.netflix.eureka.registry.ResponseCacheImpl#readOnlyCacheMap 一级读缓存,是用于客户端读取服务注册信息时候的缓存

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

      第一层为只读缓存readOnlyCacheMap,第二层为读写缓存readWriteCacheMap,第三层为registry本地注册表缓存。只读缓存每30s拉取读写缓存的值,读写缓存写入180s后过期,如果要获取的key没有value值时,则通过 registry 注册表缓存获取数据。

    分析流程:

    1. com.netflix.eureka.registry.PeerAwareInstanceRegistryImpl#init    这里是入口, 开始准备三级缓存

        @Override
        public void init(PeerEurekaNodes peerEurekaNodes) throws Exception {
            this.numberOfReplicationsLastMin.start();
            this.peerEurekaNodes = peerEurekaNodes;
            initializedResponseCache();
            scheduleRenewalThresholdUpdateTask();
            initRemoteRegionRegistry();
    
            try {
                Monitors.registerObject(this);
            } catch (Throwable e) {
                logger.warn("Cannot register the JMX monitor for the InstanceRegistry :", e);
            }
        }

    (1) com.netflix.eureka.registry.AbstractInstanceRegistry#initializedResponseCache

        @Override
        public synchronized void initializedResponseCache() {
            if (responseCache == null) {
                responseCache = new ResponseCacheImpl(serverConfig, serverCodecs, this);
            }
        }

    (2) com.netflix.eureka.registry.ResponseCacheImpl#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, 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 二级缓存

    1》这个readWriteCacheMap 的CacheLoader, 获取缓存的时候如果不存在会调用com.netflix.eureka.registry.ResponseCacheImpl#generatePayload 加载缓存

        private Value generatePayload(Key key) {
            Stopwatch tracer = null;
            try {
                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());
                            }
        ...

      可以看到是从 registry 获取一些信息,最终会调到从com.netflix.eureka.registry.AbstractInstanceRegistry#registry 三级缓存拿信息。

    2》 expireAfterWrite是指定失效策略, 其失效策略是存入之后180 s之后自动失效。

    3》 如下代码开启一个定时任务更新一级缓存的数据,responseCacheUpdateIntervalMs 默认是30 s。

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

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

      遍历readOnlyCacheMap 一级缓存的key,  和二级的做比对, 如果不一样就更新。这里比的是引用,直接用 != 进行比较。

    readOnlyCacheMap中的key 第一次被加载是在com.netflix.eureka.registry.ResponseCacheImpl#getValue。 

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

      

    【当你用心写完每一篇博客之后,你会发现它比你用代码实现功能更有成就感!】
  • 相关阅读:
    gRPC .NET Core跨平台学习
    .NET Core性能测试组件BenchmarkDotNet 支持.NET Framework Mono
    ASP.NET Core中间件(Middleware)实现WCF SOAP服务端解析
    gRPC C#学习
    中标麒麟关闭防火墙
    linux安装python
    python matplotlib.pyplot保存jpg图片失败
    python正态分布
    数据健康管理总结
    python使用statsmodel
  • 原文地址:https://www.cnblogs.com/qlqwjy/p/14534905.html
Copyright © 2011-2022 走看看