Eureka做爲服務註冊中心對整個微服務架構起着最核心的整合做用,所以對Eureka仍是有很大的必要進行深刻研究。css
Eureka 1.x版本是純基於servlet的應用。爲了與spring cloud結合使用,除了自己eureka代碼,還有個粘合模塊spring-cloud-netflix-eureka-server。在咱們啓動EurekaServer實例的時候,只用加入對於spring-cloud-starter-eureka-server的依賴便可。以後經過@EnableEurekaServer註解便可啓動一個Eureka服務器實例。先來看看這個註解是如何啓動一個Eureka服務的java
Eureka啓動,原生啓動與SpringCloudEureka啓動異同
咱們先看看做爲原生的EurekaServer啓動的過程,做爲一個Servlet應用,他的啓動入口就是他的主要ServletContextListener類(這裏是EurekaBootStrap)的contextInitialized方法node
@Override public void contextInitialized(ServletContextEvent event) { try { initEurekaEnvironment(); initEurekaServerContext(); ServletContext sc = event.getServletContext(); sc.setAttribute(EurekaServerContext.class.getName(), serverContext); } catch (Throwable e) { logger.error("Cannot bootstrap eureka server :", e); throw new RuntimeException("Cannot bootstrap eureka server :", e); } }
能夠看出主要作了兩件事,initEurekaEnvironment()與initEurekaServerContext()
對於initEurekaEnvironment()只是初始化一些必要的環境變量,因爲Eureka配置基於Spring的配置中間件Archaius,這些環境變量都是針對這個配置中間件使用的。
initEurekaServerContext()是咱們重點須要關心的,它初始化了EurekaServer須要的全部組件:git
protected void initEurekaServerContext() throws Exception { EurekaServerConfig eurekaServerConfig = new DefaultEurekaServerConfig(); //設置json與xml序列化工具 JsonXStream.getInstance().registerConverter(new V1AwareInstanceInfoConverter(), XStream.PRIORITY_VERY_HIGH); XmlXStream.getInstance().registerConverter(new V1AwareInstanceInfoConverter(), XStream.PRIORITY_VERY_HIGH); logger.info("Initializing the eureka client..."); logger.info(eurekaServerConfig.getJsonCodecName()); ServerCodecs serverCodecs = new DefaultServerCodecs(eurekaServerConfig); ApplicationInfoManager applicationInfoManager = null; //初始化EurekaClient,EurekaClient用來與其餘EurekaServer進行交互 //有可能經過guice初始化Eureka,這時eurekaClient和ApplicationInfoManager經過依賴注入先被初始化 if (eurekaClient == null) { EurekaInstanceConfig instanceConfig = isCloud(ConfigurationManager.getDeploymentContext()) ? new CloudInstanceConfig() : new MyDataCenterInstanceConfig(); applicationInfoManager = new ApplicationInfoManager( instanceConfig, new EurekaConfigBasedInstanceInfoProvider(instanceConfig).get()); EurekaClientConfig eurekaClientConfig = new DefaultEurekaClientConfig(); eurekaClient = new DiscoveryClient(applicationInfoManager, eurekaClientConfig); } else { applicationInfoManager = eurekaClient.getApplicationInfoManager(); } //初始化PeerAwareInstanceRegistry, 這個類裏面的方法就是與集羣內其餘EurekaServer實例保持業務同步的機制 PeerAwareInstanceRegistry registry; if (isAws(applicationInfoManager.getInfo())) { registry = new AwsInstanceRegistry( eurekaServerConfig, eurekaClient.getEurekaClientConfig(), serverCodecs, eurekaClient ); awsBinder = new AwsBinderDelegate(eurekaServerConfig, eurekaClient.getEurekaClientConfig(), registry, applicationInfoManager); awsBinder.start(); } else { registry = new PeerAwareInstanceRegistryImpl( eurekaServerConfig, eurekaClient.getEurekaClientConfig(), serverCodecs, eurekaClient ); } //初始化PeerEurekaNodes,裏面有定時維護Eureka集羣的業務邏輯 PeerEurekaNodes peerEurekaNodes = getPeerEurekaNodes( registry, eurekaServerConfig, eurekaClient.getEurekaClientConfig(), serverCodecs, applicationInfoManager ); //初始化EurekaServer上下文 serverContext = new DefaultEurekaServerContext( eurekaServerConfig, serverCodecs, registry, peerEurekaNodes, applicationInfoManager ); EurekaServerContextHolder.initialize(serverContext); serverContext.initialize(); logger.info("Initialized server context"); //從其餘節點中讀取註冊信息,並開放服務註冊 // Copy registry from neighboring eureka node int registryCount = registry.syncUp(); registry.openForTraffic(applicationInfoManager, registryCount); // Register all monitoring statistics. EurekaMonitors.registerAllStats(); }
總結下來,總共以下幾點:github
1.初始化並設置序列化反序列化工具
2.初始化通訊客戶端EurekaClient
3.初始化集羣通訊類PeerAwareInstanceRegistry與PeerEurekaNodes
4.初始化EurekaServer上下文serverContext
5.從其餘節點中讀取註冊信息,並開放服務註冊web
而後,因爲原生的EurekaServer利用Jersey框架初始化restApi,這裏還有:
6.載入Jersey,初始化Restful服務apispring
咱們先不談就裏面的細節,先看看在Spring-cloud下的eureka初始化是否有區別:json
對於膠水代碼,實現了大體一樣的可是略微有些區別的功能:bootstrap
@EnableDiscoveryClient @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Import(EurekaServerMarkerConfiguration.class) public @interface EnableEurekaServer { }
@EnableEurekaServer註解主要是引入EurekaServerMarkerConfiguration這個配置類,而這個配置類也很簡單:api
@Configuration public class EurekaServerMarkerConfiguration { @Bean public Marker eurekaServerMarkerBean() { return new Marker(); } class Marker { } }
這個符合基本上全部Spring-cloud生態圈的starter套路。經過一個相似於Marker的bean來啓用某個組件。核心啓動經過ConditionalOnBean來加載某些配置,在這裏這個類是:
EurekaServerAutoConfiguration,因爲Eurekaserver自己是一個Servlet應用,這個類至關於膠水代碼,將Eurekaserver須要初始化的類載入到Spring容器中管理。對於一個Servlet應用,主要初始化入口就是實現ServletContextListener的類,對於Eurekaserver是EurekaBootStrap,EurekaBootStrap初始化Eurekaserver須要的類。EurekaServerAutoConfiguration至關於將EurekaBootStrap初始化的類也初始化,同時載入到Spring容器中管理。
同時,因爲原有的EurekaServer的接口依賴Jersey,這裏的EurekaServerAutoConfiguration也要掃描Jersey實現其應該暴露的接口。同時,spring-cloud-starter-eureka-server有本身的界面,並無使用原有的Eureka界面,也是在這個類裏面加載的配置。
因此,這裏加載的Bean有:
1.Eureka DashBoard,其實就是一個Controller:
這個控制檯就是咱們經過springcloud啓動eureka以後,經過瀏覽器訪問eureka暴露的端口,看到的,例如這個:
http://eureka.didispace.com/
能夠看出,這個Controller只有eureka.dashboard.enable=true的時候纔會加載,若是不想啓用控制檯能夠設置爲false
@Bean @ConditionalOnProperty(prefix = "eureka.dashboard", name = "enabled", matchIfMissing = true) public EurekaController eurekaController() { return new EurekaController(this.applicationInfoManager); }
- 序列化反序列化工具:
@Bean public ServerCodecs serverCodecs() { return new CloudServerCodecs(this.eurekaServerConfig); }
- 初始化集羣通訊類PeerAwareInstanceRegistry與PeerEurekaNodes:
@Bean public PeerAwareInstanceRegistry peerAwareInstanceRegistry( ServerCodecs serverCodecs) { this.eurekaClient.getApplications(); // force initialization return new InstanceRegistry(this.eurekaServerConfig, this.eurekaClientConfig, serverCodecs, this.eurekaClient, this.instanceRegistryProperties.getExpectedNumberOfRenewsPerMin(), this.instanceRegistryProperties.getDefaultOpenForTrafficCount()); } @Bean @ConditionalOnMissingBean public PeerEurekaNodes peerEurekaNodes(PeerAwareInstanceRegistry registry, ServerCodecs serverCodecs) { return new PeerEurekaNodes(registry, this.eurekaServerConfig, this.eurekaClientConfig, serverCodecs, this.applicationInfoManager); }
初始化PeerAwareInstanceRegistry代碼中,咱們看到this.eurekaClient.getApplications(); 其實這段代碼沒有必要寫,已經沒有預期的做用了。咱們先來看一下EurekaClient基本原理,一樣的,這裏也是簡單過一下,往後會詳細分析。
EurekaClient的默認實現是DiscoveryClient,這個能夠經過查看EurekaClientAutoConfiguration看到。
EurekaClient主要功能就是兩個:一個是從Eurekaserver上面獲取全部註冊的服務,另外一個是將本身的服務註冊到Eurekaserver上面。因爲每次都是獲取全集,因此在註冊的服務很是多的時候,這個對網絡流量和Eurekaserver性能消耗比較大。因此每一個EurekaClient作了本身的內部緩存。兩個功能的機制如圖:
而this.eurekaClient.getApplications();只是簡單的讀取一下Eurekaserver全部註冊的服務信息緩存(一個AtomicReference類),我的感受沒什麼做用,猜測是原來的EurekaClient代碼沒有Eurekaserver全部註冊的服務信息緩存,調用getApplications()就是從服務器網絡讀取,然後來EurekaClient更新了本身的代碼,加入了緩存,而膠水代碼沒有更新。
並且,目前的Eureka原生代碼中已經在Eurekaclient初始化的時候就強制讀取一次網絡獲取Eurekaserver的全部註冊的服務信息。這段膠水代碼就更沒有必要了。
以後,注意這裏實現類是InstanceRegistry而不是PeerAwareInstanceRegistryImpl。InstanceRegistry繼承了PeerAwareInstanceRegistryImpl,並修正了原生Eureka一些設計上的與SpringCloud不兼容的地方,並且增長了context事件爲了之後作新功能作準備(猜想)。
首先,先簡單過一下PeerAwareInstanceRegistry的功能,往後咱們還會更細緻的剖析:
PeerAwareInstanceRegistry主要負責集羣中每個EurekaServer實例的服務註冊信息的同時,而且實現了一個很著名很重要的機制:自我保護機制。
先介紹兩個變量:expectedNumberOfRenewsPerMin和numberOfRenewsPerMinThreshold。其中numberOfRenewsPerMinThreshold就是RenewalPercentThreshold*numberOfRenewsPerMinThreshold;RenewalPercentThreshold是可配置的一個介於0,1之間double類型參數。還有一個計數變量renewsLastMin,記錄了上一分鐘收到的renew請求(服務維持註冊)的次數
這個自我保護機制是這樣的:
每次每一個服務新註冊時,會給expectedNumberOfRenewsPerMin加2的緣由是默認半分鐘服務向Eurekaserver心跳Renew一次。
還有另外一個機制,就是在啓動時,默認會從其餘集羣節點上面讀取全部服務註冊信息。若是一個節點都沒有訪問成功(例如這個啓動的節點就是集羣中的第一個節點),這時peerInstancesTransferEmptyOnStartup就會爲true,就會禁止用戶註冊,直到集羣中有其餘節點或者超過WaitTimeInMsWhenSyncEmpty設置的時間。
這個機制顯然不夠友好,因此膠水代碼初始化PeerAwareInstanceRegistry的擴展InstanceRegistry,加入了兩個配置參數,
ExpectedNumberOfRenewsPerMin和DefaultOpenForTrafficCount。ExpectedNumberOfRenewsPerMin默認爲1,這個爲了初始化
expectedNumberOfRenewsPerMin爲一個大於0的數,這樣expectedNumberOfRenewsPerMin在單機模式下也會刷新,這個很簡單,看一下代碼就知道,這裏再也不贅述。重點說一下DefaultOpenForTrafficCount,這個默認爲1。看下InstanceRegistry的代碼:
public InstanceRegistry(EurekaServerConfig serverConfig, EurekaClientConfig clientConfig, ServerCodecs serverCodecs, EurekaClient eurekaClient, int expectedNumberOfRenewsPerMin, int defaultOpenForTrafficCount) { super(serverConfig, clientConfig, serverCodecs, eurekaClient); this.expectedNumberOfRenewsPerMin = expectedNumberOfRenewsPerMin; this.defaultOpenForTrafficCount = defaultOpenForTrafficCount; } public void openForTraffic(ApplicationInfoManager applicationInfoManager, int count) { super.openForTraffic(applicationInfoManager, count == 0 ? this.defaultOpenForTrafficCount : count); }
構造器初始化配置,openForTraffic在原生代碼是以前提到的EurekaBootstrap contextInitialized代碼調用的;這裏是在EurekaServerInitializerConfiguration裏面初始化。這裏的openForTraffic將原來爲0的參數改成defaultOpenForTrafficCount就是1,傳入原來的openForTraffic方法。這樣保證了Eurekaserver即便是單例也能馬上正常工做;由於在單利模式下,原來的openForTraffic方法傳入的參數爲0(能夠參考以前列出的EurekaBootstrap contextInitialized代碼)
4.Eureka運行上下文
@Bean public EurekaServerContext eurekaServerContext(ServerCodecs serverCodecs, PeerAwareInstanceRegistry registry, PeerEurekaNodes peerEurekaNodes) { return new DefaultEurekaServerContext(this.eurekaServerConfig, serverCodecs, registry, peerEurekaNodes, this.applicationInfoManager); }
5.Eureka啓動類
@Bean public EurekaServerBootstrap eurekaServerBootstrap(PeerAwareInstanceRegistry registry, EurekaServerContext serverContext) { return new EurekaServerBootstrap(this.applicationInfoManager, this.eurekaClientConfig, this.eurekaServerConfig, registry, serverContext); }
6.Jersey暴露接口初始化
以後咱們會重點關注暴露的接口
@Bean public FilterRegistrationBean jerseyFilterRegistration( javax.ws.rs.core.Application eurekaJerseyApp) { FilterRegistrationBean bean = new FilterRegistrationBean(); 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. */ @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<Class<?>>(); 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<String, Object>(); 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; } @Bean public FilterRegistrationBean traceFilterRegistration( @Qualifier("webRequestLoggingFilter") Filter filter) { FilterRegistrationBean bean = new FilterRegistrationBean(); bean.setFilter(filter); bean.setOrder(Ordered.LOWEST_PRECEDENCE - 10); return bean; }
以上就是Eureka初始化基本流程,下一張咱們會更深刻針對每一個組件進行分析