当前位置 : 主页 > 编程语言 > java >

Spring Cloud——Eureka Server启动流程分析

来源:互联网 收集:自由互联 发布时间:2023-02-04
前言 SpringBoot与SpringCloud版本对应关系:https://start.spring.io/actuator/info   SpringCloud与Spring-Cloud-Alibaba版本对应关系:https://hub.fastgit.org/alibaba/spring-cloud-alibaba/wiki/%E7%89%88%E6%9C%AC%E8%AF%B4%E6%98%

前言

SpringBoot与SpringCloud版本对应关系:https://start.spring.io/actuator/info

 

SpringCloud与Spring-Cloud-Alibaba版本对应关系:https://hub.fastgit.org/alibaba/spring-cloud-alibaba/wiki/%E7%89%88%E6%9C%AC%E8%AF%B4%E6%98%8E

 

spring官网也能看到springboot与springcloud版本对应关系:https://spring.io/projects/spring-cloud-alibaba#learn

注册中心本质:存储每个客户端的注册信息,EurekaClient从EurekaServer同步获取服务注册列表,通过一定的规则选择一个服务进行调用。Eureka架构图如下:

  • 服务提供者:是一个Eureka client,向 Eureka Server注册和更新自己的信息,同时能从Eureka Server注册表中获取到其他服务的信息。

  • 服务注册中心:提供服务注册和发现的功能。每个Eureka Client向Eureka Server注册自己的信息,也可以通过Eureka Server获取到其他服务的信息达到发现和调用其他服务的目的。

  • 服务消费者:是一个Eureka client,通过Eureka Server获取注册的其他服务信息,从而找到所需要的服务发起远程调用。

  • 注册:服务提供者向Eureka Server端注册自身的元数据以供服务发现。

  • 续约:通过发送心跳到Server以维持和更新注册表中服务实例元数据的有效性。在一定时长内,Server没有收到Client的心跳信息,将默认下线,会把服务实例信息从注册表中删除。

  • 下线:服务提供方在关闭时候主动向Server注销服务实例元数据,这时服务提供方实例数据将从Server的注册表中删除。

  • 获取注册表:服务消费者Client向Server请求注册表信息,用于服务发现,从而发起远程调用。

Eureka Server源码

EnableEurekaServer注解作用

  • 通过 @EnableEurekaServer 激活EurekaServer
@Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Import({EurekaServerMarkerConfiguration.class}) public @interface EnableEurekaServer { }

此类有一个重要作用:导入EurekaServerMarkerConfiguration配置类实例化了一个Marker的bean对象,此对象是实例化核心配置类的前提条件

//激活 EurekaServerAutoConfiguration 的开关 @Configuration(proxyBeanMethods = false) public class EurekaServerMarkerConfiguration { @Bean public Marker eurekaServerMarkerBean() { return new Marker(); } class Marker { } }

自动装载核心配置类

SpringCloud对EurekaServer的封装使得发布一个EurekaServer无比简单,根据自动装载原则可以在spring-cloud-netflix-eureka-server-2.2.5.RELEASE.jar下的META-INF目录下找到 spring.factories文件

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ org.springframework.cloud.netflix.eureka.server.EurekaServerAutoConfiguration

EurekaServerAutoConfiguration

@Configuration(proxyBeanMethods = false) @Import(EurekaServerInitializerConfiguration.class) @ConditionalOnBean(EurekaServerMarkerConfiguration.Marker.class) @EnableConfigurationProperties({ EurekaDashboardProperties.class, InstanceRegistryProperties.class }) @PropertySource("classpath:/eureka/server.properties") public class EurekaServerAutoConfiguration implements WebMvcConfigurer { /** * List of packages containing Jersey resources required by the Eureka server. */ 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; }

现在我们展开来说这个Eureka服务端的自动配置类;

  • 1、这个配置类实例化的前提条件是上下文中存在EurekaServerMarkerConfiguration.Marker 这个bean,解释了上面的问题。

  • 2、通过@EnableConfigurationProperties({ EurekaDashboardProperties.class,InstanceRegistryProperties.class })导入了两个配置类:

    • 1、EurekaDashboardProperties:配置 EurekaServer的管控台
    • 2、InstanceRegistryProperties:配置期望续约数量和默认的通信数量
    • 3、通过@Import({EurekaServerInitializerConfiguration.class})引入启动配置类

EurekaServerAutoConfiguration作为自动配置类,我们看看它主要配置了哪些东西。

EurekaServerConfig

  • 初始化EurekaServer相关配置

EurekaController

  • 服务治理少不了需要一个DashBoard来可视化监控,EurekaController基于springmvc提供DashBoard相关的功能。
@Bean @ConditionalOnProperty(prefix = "eureka.dashboard", name = "enabled", matchIfMissing = true) public EurekaController eurekaController() { return new EurekaController(this.applicationInfoManager); }

PeerAwareInstanceRegistry

  • 初始化集群注册,发现注册作为主要的核心功能。
@Bean public PeerAwareInstanceRegistry peerAwareInstanceRegistry( ServerCodecs serverCodecs) { this.eurekaClient.getApplications(); // force initialization return new InstanceRegistry(this.eurekaServerConfig, this.eurekaClientConfig, serverCodecs, this.eurekaClient, this.instanceRegistryProperties.getExpectedNumberOfClientsSendingRenews(), this.instanceRegistryProperties.getDefaultOpenForTrafficCount()); }

PeerEurekaNodes

  • 初始化集群节点集合
@Bean @ConditionalOnMissingBean public PeerEurekaNodes peerEurekaNodes(PeerAwareInstanceRegistry registry, ServerCodecs serverCodecs, ReplicationClientAdditionalFilters replicationClientAdditionalFilters) { return new RefreshablePeerEurekaNodes(registry, this.eurekaServerConfig, this.eurekaClientConfig, serverCodecs, this.applicationInfoManager, replicationClientAdditionalFilters); }

EurekaServerContext

  • 基于eureka server配置注册表,集群节点集合以及服务实例初始化eureka server上下文。
@Bean @ConditionalOnMissingBean public EurekaServerContext eurekaServerContext(ServerCodecs serverCodecs, PeerAwareInstanceRegistry registry, PeerEurekaNodes peerEurekaNodes) { return new DefaultEurekaServerContext(this.eurekaServerConfig, serverCodecs, registry, peerEurekaNodes, this.applicationInfoManager); }

EurekaServerBootstrap

  • 初始化Spring Cloud包装的Eureka原生启动类
@Bean public EurekaServerBootstrap eurekaServerBootstrap(PeerAwareInstanceRegistry registry, EurekaServerContext serverContext) { return new EurekaServerBootstrap(this.applicationInfoManager, this.eurekaClientConfig, this.eurekaServerConfig, registry, serverContext); }

FilterRegistrationBean

  • 初始化jerseyFilter
@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; }

Jersey提供rpc调用

  • Jersey是一个restful风格的基于http的rpc调用框架,eureka使用它来为客户端提供远程服务。
@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; } @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 方法,在容器中存放了一个jerseyApplication对象,jerseyApplication()方法里的东西和Spring源码里扫描@Component逻辑类似,扫描@Path和@Provider标签,然后封装成beandefinition,封装到Application的set容器里。通过filter过滤器来过滤url进行映射到对象的Controller。

暴露的服务端接口

由于集成了Jersey,我们可以找到在EurekaServer的依赖包中的 eureka-core-1.9.8.jar ,可以看到一些列的XXXResource 这些类都是通过Jersey发布的供客户端调用的服务接口。

服务端接受客户端的注册 在ApplicationResource.addInstance()方法中可以看到 this.registry.register(info,"true".equals(isReplication));

@Singleton public class PeerAwareInstanceRegistryImpl extends AbstractInstanceRegistry implements PeerAwareInstanceRegistry { @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); } }

EurekaServerInitializerConfiguration

EurekaServerInitializerConfiguration实现了SmartLifecycle接口,重写了isAutoStartup方法并返回true,则会在Spring容器的finishRefresh()方法中调用EurekaServerInitializerConfiguration的start()方法,在所以在spring容器销毁的时候,就会分别调用它的stop方法。

@Configuration(proxyBeanMethods = false) public class EurekaServerInitializerConfiguration implements ServletContextAware, SmartLifecycle, Ordered { private static final Log log = LogFactory .getLog(EurekaServerInitializerConfiguration.class); @Autowired private EurekaServerConfig eurekaServerConfig; private ServletContext servletContext; @Autowired private ApplicationContext applicationContext; @Autowired private EurekaServerBootstrap eurekaServerBootstrap; private boolean running; private int order = 1; @Override public void setServletContext(ServletContext servletContext) { this.servletContext = servletContext; } @Override public void start() { // 启动一个线程 new Thread(() -> { try { //初始化EurekaServer,同时启动Eureka Server eurekaServerBootstrap.contextInitialized( EurekaServerInitializerConfiguration.this.servletContext); log.info("Started Eureka Server"); // 发布EurekaServer的注册事件 publish(new EurekaRegistryAvailableEvent(getEurekaServerConfig())); // 设置启动的状态为true EurekaServerInitializerConfiguration.this.running = true; // 发布Eureka Start 事件,另外还有其他事件,我们可以根据需要监听这些事件,然后做一些特定的业务需求 publish(new EurekaServerStartedEvent(getEurekaServerConfig())); } catch (Exception ex) { // Help! log.error("Could not initialize Eureka servlet context", ex); } }).start(); } private EurekaServerConfig getEurekaServerConfig() { return this.eurekaServerConfig; } private void publish(ApplicationEvent event) { this.applicationContext.publishEvent(event); } @Override public void stop() { this.running = false; eurekaServerBootstrap.contextDestroyed(this.servletContext); } @Override public boolean isAutoStartup() { return true; } }

eurekaServerBootstrap是spring cloud定义的类,其代码完全拷贝了Eureka启动类的实现。 这里看到在Eureka启动后,会向外发送EurekaRegistryAvailableEvent 和 EurekaServerStartedEvent 事件,我们可以根据需要订阅这两个事件,做具体的处理操作。

 

接着我们一下 EurekaServerBootstrap 做了哪些具体的操作,从源码中可以看到方法 contextInitialized(ServletContext context) 进行了环境和上下文的初始化工作,下面我们分别看一下:

public class EurekaServerBootstrap { 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); } } }

初始化执行环境——initEurekaEnvironment()

EurekaEnvironment无非就是设置了各种配置之类的东西

public class EurekaServerBootstrap { protected void initEurekaEnvironment() throws Exception { log.info("Setting the eureka configuration.."); //设置数据中心 String dataCenter = ConfigurationManager.getConfigInstance() .getString(EUREKA_DATACENTER); if (dataCenter == null) { log.info( "Eureka data center value eureka.datacenter is not set, defaulting to default"); ConfigurationManager.getConfigInstance() .setProperty(ARCHAIUS_DEPLOYMENT_DATACENTER, DEFAULT); } else { ConfigurationManager.getConfigInstance() .setProperty(ARCHAIUS_DEPLOYMENT_DATACENTER, dataCenter); } //设置 Eureka 环境 String environment = ConfigurationManager.getConfigInstance() .getString(EUREKA_ENVIRONMENT); if (environment == null) { ConfigurationManager.getConfigInstance() .setProperty(ARCHAIUS_DEPLOYMENT_ENVIRONMENT, TEST); log.info( "Eureka environment value eureka.environment is not set, defaulting to test"); } else { ConfigurationManager.getConfigInstance() .setProperty(ARCHAIUS_DEPLOYMENT_ENVIRONMENT, environment); } } }

初始化上下文——initEurekaServerContext()

public class EurekaServerBootstrap { protected void initEurekaServerContext() throws Exception { // 设置json与xml序列化工具 JsonXStream.getInstance().registerConverter(new V1AwareInstanceInfoConverter(), XStream.PRIORITY_VERY_HIGH); XmlXStream.getInstance().registerConverter(new V1AwareInstanceInfoConverter(), XStream.PRIORITY_VERY_HIGH); if (isAws(this.applicationInfoManager.getInfo())) { this.awsBinder = new AwsBinderDelegate(this.eurekaServerConfig, this.eurekaClientConfig, this.registry, this.applicationInfoManager); this.awsBinder.start(); } EurekaServerContextHolder.initialize(this.serverContext); log.info("Initialized server context"); // 从邻近的eureka节点复制注册表 int registryCount = this.registry.syncUp(); // 默认每30秒发送心跳,1分钟就是2次 // 修改eureka状态为up // 同时,这里面会开启一个定时任务,用于清理 60秒没有心跳的客户端。自动下线 this.registry.openForTraffic(this.applicationInfoManager, registryCount); // 注册所有监控统计信息 EurekaMonitors.registerAllStats(); } }

至此,EurekaServer启动完毕, EurekaServerBootstrap是完全复制了原生EurekaBootstrap的代码, 因为原生的Eureka是servlet应用,但是spring cloud的应用是运行在内嵌的Tomcat等WEB服务器里面的,所以就使用EurekaServerBootstrap来做替换,最终使Eureka能够在spring boot中使用。

 

参考: https://www.cnblogs.com/dalianpai/p/12263438.html

https://www.cnblogs.com/liukaifeng/p/10052590.html

https://www.cnblogs.com/zooking/p/13795257.html

https://www.cnblogs.com/pjjlt/p/11019247.html

网友评论