返回

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

发布时间:2023-04-09 09:15:32 240
# css# spring# springboot# 数据# 信息

前言

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 bean = new FilterRegistrationBean();
	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 bean = new FilterRegistrationBean();
	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 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

特别声明:以上内容(图片及文字)均为互联网收集或者用户上传发布,本站仅提供信息存储服务!如有侵权或有涉及法律问题请联系我们。
举报
评论区(0)
按点赞数排序
用户头像
精选文章
thumb 中国研究员首次曝光美国国安局顶级后门—“方程式组织”
thumb 俄乌线上战争,网络攻击弥漫着数字硝烟
thumb 从网络安全角度了解俄罗斯入侵乌克兰的相关事件时间线
下一篇
Spring Cloud——Eureka工作原理 2023-04-09 05:56:57