Spring Cloud Eureka源代码解析(1)Eureka启动,原生启动与SpringCloudEureka启动异同

Eureka作为服务注册中心对整个微服务架构起着最核心的整合作用,因此对Eureka还是有很大的必要进行深入研究。

Eureka 1.x版本是纯基于servlet的应用。为了与spring cloud结合使用,除了本身eureka代码,还有个粘合模块spring-cloud-netflix-eureka-server。在我们启动EurekaServer实例的时候,只用加入对于spring-cloud-starter-eureka-server的依赖即可。之后通过@EnableEurekaServer注解即可启动一个Eureka服务器实例。先来看看这个注解是如何启动一个Eureka服务的

Eureka启动,原生启动与SpringCloudEureka启动异同

我们先看看作为原生的EurekaServer启动的过程,作为一个Servlet应用,他的启动入口就是他的主要ServletContextListener类(这里是EurekaBootStrap)的contextInitialized方法

1@Override 2public void contextInitialized(ServletContextEvent event) { 3 try { 4 initEurekaEnvironment(); 5 initEurekaServerContext(); 6 7 ServletContext sc = event.getServletContext(); 8 sc.setAttribute(EurekaServerContext.class.getName(), serverContext); 9 } catch (Throwable e) { 10 logger.error("Cannot bootstrap eureka server :", e); 11 throw new RuntimeException("Cannot bootstrap eureka server :", e); 12 } 13}

可以看出主要做了两件事,initEurekaEnvironment()与initEurekaServerContext()
对于initEurekaEnvironment()只是初始化一些必要的环境变量,由于Eureka配置基于Spring的配置中间件Archaius,这些环境变量都是针对这个配置中间件使用的。
initEurekaServerContext()是我们重点需要关心的,它初始化了EurekaServer需要的所有组件:

1protected void initEurekaServerContext() throws Exception { 2 EurekaServerConfig eurekaServerConfig = new DefaultEurekaServerConfig(); 3 4 //设置json与xml序列化工具 5 JsonXStream.getInstance().registerConverter(new V1AwareInstanceInfoConverter(), XStream.PRIORITY_VERY_HIGH); 6 XmlXStream.getInstance().registerConverter(new V1AwareInstanceInfoConverter(), XStream.PRIORITY_VERY_HIGH); 7 logger.info("Initializing the eureka client..."); 8 logger.info(eurekaServerConfig.getJsonCodecName()); 9 ServerCodecs serverCodecs = new DefaultServerCodecs(eurekaServerConfig); 10 11 12 ApplicationInfoManager applicationInfoManager = null; 13 14 //初始化EurekaClient,EurekaClient用来与其他EurekaServer进行交互 15 //有可能通过guice初始化Eureka,这时eurekaClient和ApplicationInfoManager通过依赖注入先被初始化 16 if (eurekaClient == null) { 17 EurekaInstanceConfig instanceConfig = isCloud(ConfigurationManager.getDeploymentContext()) 18 ? new CloudInstanceConfig() 19 : new MyDataCenterInstanceConfig(); 20 21 applicationInfoManager = new ApplicationInfoManager( 22 instanceConfig, new EurekaConfigBasedInstanceInfoProvider(instanceConfig).get()); 23 24 EurekaClientConfig eurekaClientConfig = new DefaultEurekaClientConfig(); 25 eurekaClient = new DiscoveryClient(applicationInfoManager, eurekaClientConfig); 26 } else { 27 applicationInfoManager = eurekaClient.getApplicationInfoManager(); 28 } 29 30 //初始化PeerAwareInstanceRegistry, 这个类里面的方法就是与集群内其他EurekaServer实例保持业务同步的机制 31 PeerAwareInstanceRegistry registry; 32 if (isAws(applicationInfoManager.getInfo())) { 33 registry = new AwsInstanceRegistry( 34 eurekaServerConfig, 35 eurekaClient.getEurekaClientConfig(), 36 serverCodecs, 37 eurekaClient 38 ); 39 awsBinder = new AwsBinderDelegate(eurekaServerConfig, eurekaClient.getEurekaClientConfig(), registry, applicationInfoManager); 40 awsBinder.start(); 41 } else { 42 registry = new PeerAwareInstanceRegistryImpl( 43 eurekaServerConfig, 44 eurekaClient.getEurekaClientConfig(), 45 serverCodecs, 46 eurekaClient 47 ); 48 } 49 50 //初始化PeerEurekaNodes,里面有定时维护Eureka集群的业务逻辑 51 PeerEurekaNodes peerEurekaNodes = getPeerEurekaNodes( 52 registry, 53 eurekaServerConfig, 54 eurekaClient.getEurekaClientConfig(), 55 serverCodecs, 56 applicationInfoManager 57 ); 58 59 60 //初始化EurekaServer上下文 61 serverContext = new DefaultEurekaServerContext( 62 eurekaServerConfig, 63 serverCodecs, 64 registry, 65 peerEurekaNodes, 66 applicationInfoManager 67 ); 68 69 EurekaServerContextHolder.initialize(serverContext); 70 71 serverContext.initialize(); 72 logger.info("Initialized server context"); 73 74 //从其他节点中读取注册信息,并开放服务注册 75 // Copy registry from neighboring eureka node 76 int registryCount = registry.syncUp(); 77 registry.openForTraffic(applicationInfoManager, registryCount); 78 79 // Register all monitoring statistics. 80 EurekaMonitors.registerAllStats(); 81}

总结下来,总共如下几点:

1.初始化并设置序列化反序列化工具
2.初始化通信客户端EurekaClient
3.初始化集群通信类PeerAwareInstanceRegistry与PeerEurekaNodes
4.初始化EurekaServer上下文serverContext
5.从其他节点中读取注册信息,并开放服务注册

然后,由于原生的EurekaServer利用Jersey框架初始化restApi,这里还有:
6.载入Jersey,初始化Restful服务api

我们先不谈就里面的细节,先看看在Spring-cloud下的eureka初始化是否有区别:

对于胶水代码,实现了大致同样的但是略微有些区别的功能:

1@EnableDiscoveryClient 2@Target(ElementType.TYPE) 3@Retention(RetentionPolicy.RUNTIME) 4@Documented 5@Import(EurekaServerMarkerConfiguration.class) 6public @interface EnableEurekaServer { 7 8}

@EnableEurekaServer注解主要是引入EurekaServerMarkerConfiguration这个配置类,而这个配置类也很简单:

1@Configuration 2public class EurekaServerMarkerConfiguration { 3 4 @Bean 5 public Marker eurekaServerMarkerBean() { 6 return new Marker(); 7 } 8 9 class Marker { 10 } 11}

这个符合基本上所有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

1@Bean 2@ConditionalOnProperty(prefix = "eureka.dashboard", name = "enabled", matchIfMissing = true) 3public EurekaController eurekaController() { 4 return new EurekaController(this.applicationInfoManager); 5}
  1. 序列化反序列化工具:

    @Bean public ServerCodecs serverCodecs() { return new CloudServerCodecs(this.eurekaServerConfig); }

  2. 初始化集群通信类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做了自己的内部缓存。两个功能的机制如图:

image

而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请求(服务维持注册)的次数

这个自我保护机制是这样的:
image

每次每个服务新注册时,会给expectedNumberOfRenewsPerMin加2的原因是默认半分钟服务向Eurekaserver心跳Renew一次。

还有另一个机制,就是在启动时,默认会从其他集群节点上面读取所有服务注册信息。如果一个节点都没有访问成功(例如这个启动的节点就是集群中的第一个节点),这时peerInstancesTransferEmptyOnStartup就会为true,就会禁止用户注册,直到集群中有其他节点或者超过WaitTimeInMsWhenSyncEmpty设置的时间。

这个机制显然不够友好,所以胶水代码初始化PeerAwareInstanceRegistry的扩展InstanceRegistry,加入了两个配置参数,
ExpectedNumberOfRenewsPerMin和DefaultOpenForTrafficCount。ExpectedNumberOfRenewsPerMin默认为1,这个为了初始化
expectedNumberOfRenewsPerMin为一个大于0的数,这样expectedNumberOfRenewsPerMin在单机模式下也会刷新,这个很简单,看一下代码就知道,这里不再赘述。重点说一下DefaultOpenForTrafficCount,这个默认为1。看下InstanceRegistry的代码:

1public InstanceRegistry(EurekaServerConfig serverConfig, 2 EurekaClientConfig clientConfig, ServerCodecs serverCodecs, 3 EurekaClient eurekaClient, int expectedNumberOfRenewsPerMin, 4 int defaultOpenForTrafficCount) { 5 super(serverConfig, clientConfig, serverCodecs, eurekaClient); 6 7 this.expectedNumberOfRenewsPerMin = expectedNumberOfRenewsPerMin; 8 this.defaultOpenForTrafficCount = defaultOpenForTrafficCount; 9} 10 11public void openForTraffic(ApplicationInfoManager applicationInfoManager, int count) { 12 super.openForTraffic(applicationInfoManager, 13 count == 0 ? this.defaultOpenForTrafficCount : count); 14}

构造器初始化配置,openForTraffic在原生代码是之前提到的EurekaBootstrap contextInitialized代码调用的;这里是在EurekaServerInitializerConfiguration里面初始化。这里的openForTraffic将原来为0的参数改为defaultOpenForTrafficCount就是1,传入原来的openForTraffic方法。这样保证了Eurekaserver即使是单例也能立刻正常工作;因为在单利模式下,原来的openForTraffic方法传入的参数为0(可以参考之前列出的EurekaBootstrap contextInitialized代码)

4.Eureka运行上下文

1@Bean 2public EurekaServerContext eurekaServerContext(ServerCodecs serverCodecs, 3 PeerAwareInstanceRegistry registry, PeerEurekaNodes peerEurekaNodes) { 4 return new DefaultEurekaServerContext(this.eurekaServerConfig, serverCodecs, 5 registry, peerEurekaNodes, this.applicationInfoManager); 6} 7

5.Eureka启动类

1@Bean 2public EurekaServerBootstrap eurekaServerBootstrap(PeerAwareInstanceRegistry registry, 3 EurekaServerContext serverContext) { 4 return new EurekaServerBootstrap(this.applicationInfoManager, 5 this.eurekaClientConfig, this.eurekaServerConfig, registry, 6 serverContext); 7}

6.Jersey暴露接口初始化
之后我们会重点关注暴露的接口

1@Bean 2public FilterRegistrationBean jerseyFilterRegistration( 3 javax.ws.rs.core.Application eurekaJerseyApp) { 4 FilterRegistrationBean bean = new FilterRegistrationBean(); 5 bean.setFilter(new ServletContainer(eurekaJerseyApp)); 6 bean.setOrder(Ordered.LOWEST_PRECEDENCE); 7 bean.setUrlPatterns( 8 Collections.singletonList(EurekaConstants.DEFAULT_PREFIX + "/*")); 9 10 return bean; 11} 12 13/** * Construct a Jersey {@link javax.ws.rs.core.Application} with all the resources * required by the Eureka server. */ 14@Bean 15public javax.ws.rs.core.Application jerseyApplication(Environment environment, 16 ResourceLoader resourceLoader) { 17 18 ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider( 19 false, environment); 20 21 // Filter to include only classes that have a particular annotation. 22 // 23 provider.addIncludeFilter(new AnnotationTypeFilter(Path.class)); 24 provider.addIncludeFilter(new AnnotationTypeFilter(Provider.class)); 25 26 // Find classes in Eureka packages (or subpackages) 27 // 28 Set<Class<?>> classes = new HashSet<Class<?>>(); 29 for (String basePackage : EUREKA_PACKAGES) { 30 Set<BeanDefinition> beans = provider.findCandidateComponents(basePackage); 31 for (BeanDefinition bd : beans) { 32 Class<?> cls = ClassUtils.resolveClassName(bd.getBeanClassName(), 33 resourceLoader.getClassLoader()); 34 classes.add(cls); 35 } 36 } 37 38 // Construct the Jersey ResourceConfig 39 // 40 Map<String, Object> propsAndFeatures = new HashMap<String, Object>(); 41 propsAndFeatures.put( 42 // Skip static content used by the webapp 43 ServletContainer.PROPERTY_WEB_PAGE_CONTENT_REGEX, 44 EurekaConstants.DEFAULT_PREFIX + "/(fonts|images|css|js)/.*"); 45 46 DefaultResourceConfig rc = new DefaultResourceConfig(classes); 47 rc.setPropertiesAndFeatures(propsAndFeatures); 48 49 return rc; 50} 51@Bean 52public FilterRegistrationBean traceFilterRegistration( 53 @Qualifier("webRequestLoggingFilter") Filter filter) { 54 FilterRegistrationBean bean = new FilterRegistrationBean(); 55 bean.setFilter(filter); 56 bean.setOrder(Ordered.LOWEST_PRECEDENCE - 10); 57 return bean; 58}

以上就是Eureka初始化基本流程,下一张我们会更深入针对每个组件进行分析

点赞
收藏

评论区

加载中...

相关推荐

手写Java HashMap源码

HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22

SpringCloud注册中心高可用搭建

SpringCloud的注册中心可以由Eureka、Consul、Zookeeper、ETCD等来实现,这里推荐使用SpringCloudEureka来实现注册中心,它基于Netfilix的Eureka做了二次封装,完成分布式服务中服务治理的功能,微服务系统中的服务注册与发现都通过这个注册中心来进行管理。引入EurekaServer依赖

Spring Cloud Eureka 全解 (6)

本文基于SpringCloudDalston.SR5(https://www.oschina.net/action/GoToLink?urlhttp%3A%2F%2Fcloud.spring.io%2Fspringcloudstatic%2FDalston.SR5%2F)Eureka服务实例启动时,是否会立刻向EurekaServer注册?

Spring Cloud系列教程(六):服务注册与发现Consul(Finchley版本)

一、前言在微服务领域,服务注册与发现是其中很重要的一个模块,主要用于服务治理问题;在分布式Dubbo中常用的服务发现与注册中心是Zookeeper,Cosul与其类似,在SpringCloud刚占领市场的时候,SpringCloud微服务框架默认使用的注册中心组建是Eureka,总所周知,Eureka已经开始闭源了,

SpringCloud常用组件

springcloud中有五大核心组件Eureka、Ribbon、Feign、Hystrix、Zuul,简单记录如下。Eureka是微服务架构中的注册中心,专门负责服务的注册与发现。EurekaClient组件专门负责将服务的信息注册到EurekaServer中,而EurekaServer是一个注册中心,里面有一个注册表,保存了各服务所在

Spring Cloud Eureka 服务关闭但是未从注册中心删除 自我保护机制

自我保护背景首先对Eureka注册中心需要了解的是Eureka各个节点都是平等的,没有ZK中角色的概念,即使N1个节点挂掉也不会影响其他节点的正常运行。默认情况下,如果EurekaServer在一定时间内(默认90秒)没有接收到某个微服务实例的心跳,EurekaServer将会移除该实例。但是当网络分区故障发生时,微服务与EurekaSer