Eureka的高可用架构
如图为Eureka的高级架构图,该图片来自于Eureka开源代码的文档,地址为https://github.com/Netflix/eureka/wiki/Eureka-at-a-glance 。
从图可以看出在这个体系中,有2个角色,即Eureka Server和Eureka Client。而Eureka Client又分为Applicaton Service和Application Client,即服务提供者、服务消费者。 每个区域有一个Eureka集群,并且每个区域至少有一个eureka服务器可以处理区域故障,以防服务器瘫痪。
Eureka Client向Eureka Serve注册,并将自己的一些客户端信息发送Eureka Serve。然后,Eureka Client通过向Eureka Serve发送心跳(每30秒)来续约服务的。 如果客户端持续不能续约,那么,它将在大约90秒内从服务器注册表中删除。 注册信息和续订被复制到集群中的Eureka Serve所有节点。 来自任何区域的Eureka Client都可以查找注册表信息(每30秒发生一次)。根据这些注册表信息,Application Client可以远程调用Applicaton Service来消费服务。
Eureka相关概念
服务注册 Register
服务提供者在启动时会将自己的信息(比如IP地址、端口,运行状况指示符URL,主页等)注册到Eureka Server, Eureka Server收到信息后, 会将数据信息存储在一个双层结构的Map中,其中第一层的key是服务名,第二层的key是具体服务的实例名。
如果有多个Eureka Server,一个服务提供者向其中一个Eureka Server注册了,这个Eureka Server会向集群内的其它Eureka Server转发这个服务提供者的注册信息,从而实现实现Eureka Server之间的服务同步。
在注册完成成后,Eureka客户会每隔30秒发送一次心跳来续约。 通过续约来告知Eureka Server该Eureka客户仍然存在,没有出现问题。 正常情况下,如果Eureka Server在90秒没有收到Eureka客户的续约,它会将实例从其注册表中删除。 建议不要更改续约间隔。
当服务实例正常关闭时,它会发送一个服务下线的消息给注册中心,注册中心收到信息后,会将该服务实例状态置为下线,并把该信息传播出去。该下线请求不会自动完成,它需要调用以下内容:DiscoveryManager.getInstance().shutdownComponent()
Eureka客户端从服务器获取注册表信息,并将其缓存在本地。客户端会使用该信息查找其他服务,从而进行远程调用。该注册列表信息定期(每30秒钟)更新一次。每次返回注册列表信息可能与Eureka客户端的缓存信息不同, Eureka客户端自动处理。如果由于某种原因导致注册列表信息不能及时匹配,Eureka客户端则会重新获取整个注册表信息。 Eureka服务器缓存注册列表信息,整个注册表以及每个应用程序的信息进行了压缩,压缩内容和没有压缩的内容完全相同。Eureka客户端和Eureka 服务器可以使用JSON / XML格式进行通讯。在默认的情况下Eureka客户端使用压缩JSON格式来获取注册列表的信息。
有时候,服务实例可能无法正常提供服务,而注册中心没有收到服务下线的信息。注册中心会创建一个定时任务,将超过一定时间没有服务续约消息的服务实例从服务清单中剔除。
上面讲到失效剔除时,会将超过一定时间没有收到服务续约消息的实例从服务清单中剔除掉,在这中间还有一个逻辑。如果在运行期间,统计心跳成功的比例低于85%(心跳阈值),注册中心会将当前服务清单中的实例注册信息保护起来,让这些实例不会过期。但是在这种情况下,若服务实例出现问题,那么服务消费者可能会拿到实际已经不能正常运行的服务实例,就会出现调用失败的情况,所以客户端需要有容错机制,比如请求重试,或断路器等。
但是有一个定时任务默认每15分钟执行一次,会根据运行状况重新计算心跳阈值;但也可能不重新计算,这时,Eureka Server的自我保护状态会一直存在。
如果要关闭自我保护机制,可以将eureka.server.enable-self-preservation设置为false,以确保注册中心将不可用的服务实例及时剔除。
源码分析
Eureka分为注册中心,也就是Eureka-Server,或者称服务端,注册到服务端的服务实例称为客户端,客户端又抽象成服务提供者,服务消费者。其中在Eureka中,对于一客户端既是服务提供者,同时也可能是服务消费者。服务端也会向另一个服务端实例注册自己的信息,从而实现Server集群。
Eureka Client启动过程
首先是启动服务,需要添加@EnableDiscoveryClient 或@EnableEurekaClient,
从Spring Cloud Edgware开始,可省略。只需加上相关依赖,并进行相应配置,即可将微服务注册到服务发现组件上。
@EnableDiscoveryClient和@EnableEurekaClient共同点就是:都是让注册中心能够发现,扫描到该服务。
不同点:@EnableEurekaClient只适用于Eureka作为注册中心,@EnableDiscoveryClient 可以是其他注册中心。
下面开始分析源码: springboot版本:2.0.3 springcloud:Finchley.RELEASE
1)@EnableDiscoveryClient
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import({EnableDiscoveryClientImportSelector.class})
public @interface EnableDiscoveryClient {
boolean autoRegister() default true;
}
2)EnableDiscoveryClientImportSelector
@Order(2147483547)
public class EnableDiscoveryClientImportSelector extends SpringFactoryImportSelector<EnableDiscoveryClient> {
public EnableDiscoveryClientImportSelector() {
}
public String[] selectImports(AnnotationMetadata metadata) {
// 1.核心功能在这里,获取需要注册到Spring的类
String[] imports = super.selectImports(metadata);
AnnotationAttributes attributes = AnnotationAttributes.fromMap
(metadata.getAnnotationAttributes(this.getAnnotationClass().getName(), true));
boolean autoRegister = attributes.getBoolean("autoRegister");
// 2.autoRegister默认为true,同时则注册AutoServiceRegistrationConfiguration类到Spring中
if (autoRegister) {
List<String> importsList = new ArrayList(Arrays.asList(imports));
importsList.add("org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationConfiguration");
imports = (String[])importsList.toArray(new String[0]);
} else {
Environment env = this.getEnvironment();
if (ConfigurableEnvironment.class.isInstance(env)) {
ConfigurableEnvironment configEnv = (ConfigurableEnvironment)env;
LinkedHashMap<String, Object> map = new LinkedHashMap();
map.put("spring.cloud.service-registry.auto-registration.enabled", false);
MapPropertySource propertySource = new MapPropertySource("springCloudDiscoveryClient", map);
configEnv.getPropertySources().addLast(propertySource);
}
}
return imports;
}
...
}
3)super.selectImports(metadata)即在SpringFactoryImportSelector.selectImports(metadata)
public abstract class SpringFactoryImportSelector<T>
implements DeferredImportSelector, BeanClassLoaderAware, EnvironmentAware {
...
@Override
public String[] selectImports(AnnotationMetadata metadata) {
// 1.默认enabled值为true
if (!isEnabled()) {
return new String[0];
}
...
// 2.核心功能
List<String> factories = new ArrayList<>(new LinkedHashSet<>(SpringFactoriesLoader
.loadFactoryNames(this.annotationClass, this.beanClassLoader)));
...
return factories.toArray(new String[factories.size()]);
}
public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {
// 1.factoryClassName值为org.springframework.cloud.client.discovery.EnableDiscoveryClient
String factoryClassName = factoryClass.getName();
try {
// 2.获取所有 META-INF/spring.factories文件
Enumeration<URL> urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
List<String> result = new ArrayList<String>();
// 3.遍历所有spring.factories文件
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url));
String factoryClassNames = properties.getProperty(factoryClassName);
// 4.获取properties中key为EnableDiscoveryClient对应的value值列表
result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(factoryClassNames)));
}
return result;
}
...
}
spring.factories文件
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.netflix.eureka.config.EurekaClientConfigServerAutoConfiguration,\
org.springframework.cloud.netflix.eureka.config.EurekaDiscoveryClientConfigServiceAutoConfiguration,\
org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration,\
org.springframework.cloud.netflix.ribbon.eureka.RibbonEurekaAutoConfiguration,\
org.springframework.cloud.netflix.eureka.EurekaDiscoveryClientConfiguration
org.springframework.cloud.bootstrap.BootstrapConfiguration=\
org.springframework.cloud.netflix.eureka.config.EurekaDiscoveryClientConfigServiceBootstrapConfiguration
将以上类注册到spring容器中,EurekaClient的关键功能就在EurekaClientConfigServerAutoConfiguration中,下面我们一起来看下这个类:
@ConditionalOnBean({EurekaDiscoveryClientConfiguration.class})
@ConditionalOnProperty(
value = {"spring.cloud.config.discovery.enabled"},
matchIfMissing = false
)
public class EurekaDiscoveryClientConfigServiceAutoConfiguration {
@Autowired
private ConfigurableApplicationContext context;
public EurekaDiscoveryClientConfigServiceAutoConfiguration() {
}
@PostConstruct
public void init() {
if (this.context.getParent() != null && this.context.getBeanNamesForType(EurekaClient.class).length > 0 && this.context.getParent().getBeanNamesForType(EurekaClient.class).length > 0) {
((EurekaClient)this.context.getParent().getBean(EurekaClient.class)).shutdown();
}
}
}
init()方法中依赖EurekaClient.class, EurekaClient主要是一个接口,该接口定义了Eureka客户端的主要功能,并定义了默认实现类DiscoveryClient,该类包含了Eureka Client向Eureka Server的相关方法。并且它是一个单例模式,而EurekaClient又继承了LookupService接口。它们之间的关系如图所示。
以上是@EnableDiscoveryClient的启动逻辑,从EnableDiscoveryClient的注释中我们可以看到,它最终主要是用来开启com.netflix.discovery.DiscoveryClient的实例。从该类的注释可以看出,该类包含服务注册、服务续约、服务下线、获取服务等功能。DiscoveryClient的详细解析请见下文。
最后推荐一篇详解博客: