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初始化基本流程,下一张咱们会更深刻针对每一个组件进行分析