前言:
Eureka作为一个服务注册中心,主要功能就是服务注册与服务发现。
微服务框架中,服务注册与发现既是基础功能也是核心功能。
Eureka分为服务端和客户端。
服务端也称为服务注册中心,它同其他服务注册中心一样,支持高可用配置。在项目中使用@EnableEurekaServer实现即可
客户端主要处理服务的注册与发现,每一个服务提供者和消费者都可以称为客户端。在项目中使用@EnableEurekaClient实现即可。
有关于Eureka的使用可以参考笔者另一篇文章 https://blog.csdn.net/qq_26323323/article/details/78652849
本文主要介绍Eureka作为客户端的应用,也就是在应用添加@EnableEurekaClient注解的应用
1.@EnableEurekaClient解析
客户端应用通过添加@EnableEurekaClient注解,再在配置文件中添加eureka相关配置即可实现服务的注册与发现,还是颇为神奇的,主要功能应该都几种在@EnableEurekaClient这个注解中,下面我们来剖析一下这个注解。
1)@EnableEurekaClient源码如下:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@EnableDiscoveryClient // 主要注解就是这个
public @interface EnableEurekaClient {
}
// @EnableDiscoveryClient
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(EnableDiscoveryClientImportSelector.class)// 主要就是为了导入该类
public @interface EnableDiscoveryClient {
boolean autoRegister() default true;
}
2)EnableDiscoveryClientImportSelector.java
@Order(Ordered.LOWEST_PRECEDENCE - 100)
public class EnableDiscoveryClientImportSelector
extends SpringFactoryImportSelector {
@Override
public String[] selectImports(AnnotationMetadata metadata) {
// 1.核心功能在这里,获取需要注册到Spring的类
String[] imports = super.selectImports(metadata);// 在3)中详解
AnnotationAttributes attributes = AnnotationAttributes.fromMap(
metadata.getAnnotationAttributes(getAnnotationClass().getName(), true));
boolean autoRegister = attributes.getBoolean("autoRegister");
// 2.autoRegister默认为true,同时则注册AutoServiceRegistrationConfiguration类到Spring中
if (autoRegister) {
List importsList = new ArrayList(Arrays.asList(imports));
importsList.add("org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationConfiguration");
imports = importsList.toArray(new String[0]);
}
return imports;
}
...
}
3)super.selectImports(metadata)即在SpringFactoryImportSelector.selectImports(metadata)
public abstract class SpringFactoryImportSelector
implements DeferredImportSelector, BeanClassLoaderAware, EnvironmentAware {
...
@Override
public String[] selectImports(AnnotationMetadata metadata) {
// 1.默认enabled值为true
if (!isEnabled()) {
return new String[0];
}
...
// 2.主要功能在这里
List factories = new ArrayList(new LinkedHashSet(SpringFactoriesLoader
.loadFactoryNames(this.annotationClass, this.beanClassLoader)));
...
return factories.toArray(new String[factories.size()]);
}
// SpringFactoriesLoader.loadFactoryNames(this.annotationClass, this.beanClassLoader)
public static List loadFactoryNames(Class factoryClass, ClassLoader classLoader) {
// 1.factoryClassName值为org.springframework.cloud.client.discovery.EnableDiscoveryClient
String factoryClassName = factoryClass.getName();
try {
// 2.获取所有 META-INF/spring.factories文件
Enumeration urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
List result = new ArrayList();
// 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;
}
...
}
注意:org.springframework.cloud.client.discovery.EnableDiscoveryClient对应的value值,可以在spring-cloud-netflix-eureka-client-1.3.1.RELEASE-sources.jar下META-INF/spring.factories文件中获取,具体值为
org.springframework.cloud.client.discovery.EnableDiscoveryClient=\
org.springframework.cloud.netflix.eureka.EurekaDiscoveryClientConfiguration
总结:所以上述注册到Spring中的类为两个:
org.springframework.cloud.netflix.eureka.EurekaDiscoveryClientConfiguration
org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationConfiguration
同时我们还注意到eureka-client下META-INF/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
通过笔者的另一篇文章https://blog.csdn.net/qq_26323323/article/details/81204284 可知, EnableAutoConfiguration对应的value值列表中的类会在SpringBoot项目启动的时候注册到Spring容器中,EurekaClient的关键功能就在EurekaClientConfigServerAutoConfiguration中,下面我们一起来看下这个类
2.EurekaClientConfigServerAutoConfiguration功能解析
源码如下:
@Configuration
@EnableConfigurationProperties
@ConditionalOnClass({ EurekaInstanceConfigBean.class, EurekaClient.class,
ConfigServerProperties.class })
public class EurekaClientConfigServerAutoConfiguration {
...
@PostConstruct
public void init() {
if (this.instance == null || this.server == null) {
return;
}
String prefix = this.server.getPrefix();
if (StringUtils.hasText(prefix)) {
this.instance.getMetadataMap().put("configPath", prefix);
}
}
}
通过该类@ConditionalOnClass注解可知,EurekaClientConfigServerAutoConfiguration类的产生需要一些条件,需要EurekaInstanceConfigBean.class, EurekaClient.class,ConfigServerProperties.class这三个类先行产生。
我们重点关注下EurekaClient.class,源码如下:
@ImplementedBy(DiscoveryClient.class)
public interface EurekaClient extends LookupService {}
主要是一个接口,并定义了默认实现类DiscoveryClient,该接口定义了Eureka客户端的主要功能,包括获取服务URL、注册当前服务等功能。
注意:这里使用了Google-Guice框架,这是一个轻量级的DI框架。具体使用读者可参考https://blog.csdn.net/derekjiang/article/details/7231490
3.DiscoveryClient
实际没有绝对的服务消费者和服务提供者,每一个服务提供者也可以是一个服务消费者。
消费者和提供者的主要功能点有:服务注册、服务续约、服务下线、服务调用
下面根据功能点来对照各自的源码,以下方法可在com.netflix.discovery.DiscoveryClient中找到
1)服务注册(发送注册请求到注册中心)
boolean register() throws Throwable {
...
EurekaHttpResponse httpResponse;
try {
// 主要的注册功能就是这句话
// 真正实现在AbstractJerseyEurekaHttpClient.register()
httpResponse = eurekaTransport.registrationClient.register(instanceInfo);
}
...
return httpResponse.getStatusCode() == 204;
}
// AbstractJerseyEurekaHttpClient.register()
public EurekaHttpResponse register(InstanceInfo info) {
String urlPath = "apps/" + info.getAppName();
ClientResponse response = null;
try {
// 1.构造一个HTTP请求
Builder resourceBuilder = jerseyClient.resource(serviceUrl).path(urlPath).getRequestBuilder();
addExtraHeaders(resourceBuilder);
// 2.封装请求类型和返回类型,将当前服务的元信息封装为InstanceInfo,
// 发送post请求到serviceUrl,serviceUrl即我们在配置文件中配置的defaultZone
response = resourceBuilder
.header("Accept-Encoding", "gzip")
.type(MediaType.APPLICATION_JSON_TYPE)
.accept(MediaType.APPLICATION_JSON)
.post(ClientResponse.class, info);
// 3.返回响应状态
return anEurekaHttpResponse(response.getStatus()).headers(headersOf(response)).build();
} finally {
if (logger.isDebugEnabled()) {
logger.debug("Jersey HTTP POST {}/{} with instance {}; statusCode={}", serviceUrl, urlPath, info.getId(),
response == null ? "N/A" : response.getStatus());
}
if (response != null) {
response.close();
}
}
}
2)服务续约(本质就是发送当前应用的心跳请求到注册中心)
boolean renew() {
EurekaHttpResponse httpResponse;
try {
// 1.本质就是发送心跳请求
// 2.真正实现为 AbstractJerseyEurekaHttpClient.sendHeartBeat()
httpResponse = eurekaTransport.registrationClient.sendHeartBeat(instanceInfo.getAppName(), instanceInfo.getId(), instanceInfo, null);
logger.debug("{} - Heartbeat status: {}", PREFIX + appPathIdentifier, httpResponse.getStatusCode());
// 2.如果请求失败,则调用注册服务请求
if (httpResponse.getStatusCode() == 404) {
REREGISTER_COUNTER.increment();
logger.info("{} - Re-registering apps/{}", PREFIX + appPathIdentifier, instanceInfo.getAppName());
return register();
}
return httpResponse.getStatusCode() == 200;
} catch (Throwable e) {
logger.error("{} - was unable to send heartbeat!", PREFIX + appPathIdentifier, e);
return false;
}
}
// AbstractJerseyEurekaHttpClient.sendHeartBeat()
public EurekaHttpResponse sendHeartBeat(String appName, String id, InstanceInfo info, InstanceStatus overriddenStatus) {
String urlPath = "apps/" + appName + '/' + id;
ClientResponse response = null;
try {
// 主要就是将当前实例的元信息(InstanceInfo)以及状态(UP)通过HTTP请求发送到serviceUrl
WebResource webResource = jerseyClient.resource(serviceUrl)
.path(urlPath)
.queryParam("status", info.getStatus().toString())
.queryParam("lastDirtyTimestamp", info.getLastDirtyTimestamp().toString());
if (overriddenStatus != null) {
webResource = webResource.queryParam("overriddenstatus", overriddenStatus.name());
}
Builder requestBuilder = webResource.getRequestBuilder();
addExtraHeaders(requestBuilder);
response = requestBuilder.put(ClientResponse.class);
EurekaHttpResponseBuilder eurekaResponseBuilder = anEurekaHttpResponse(response.getStatus(), InstanceInfo.class).headers(headersOf(response));
if (response.hasEntity()) {
eurekaResponseBuilder.entity(response.getEntity(InstanceInfo.class));
}
return eurekaResponseBuilder.build();
} finally {
if (logger.isDebugEnabled()) {
logger.debug("Jersey HTTP PUT {}/{}; statusCode={}", serviceUrl, urlPath, response == null ? "N/A" : response.getStatus());
}
if (response != null) {
response.close();
}
}
}
3.服务调用(本质就是获取调用服务名所对应的服务提供者实例信息,包括IP、port等)
public List getInstancesByVipAddress(String vipAddress, boolean secure) {
return getInstancesByVipAddress(vipAddress, secure, instanceRegionChecker.getLocalRegion());
}
//getInstancesByVipAddress()
public List getInstancesByVipAddress(String vipAddress, boolean secure,
@Nullable String region) {
if (vipAddress == null) {
throw new IllegalArgumentException(
"Supplied VIP Address cannot be null");
}
Applications applications;
// 1.判断服务提供方是否当前region,若是的话直接从localRegionApps中获取
if (instanceRegionChecker.isLocalRegion(region)) {
applications = this.localRegionApps.get();
// 2.否则的话从远程region获取
} else {
applications = remoteRegionVsApps.get(region);
if (null == applications) {
logger.debug("No applications are defined for region {}, so returning an empty instance list for vip "
+ "address {}.", region, vipAddress);
return Collections.emptyList();
}
}
// 3.从applications中获取服务名称对应的实例名称列表
if (!secure) {
return applications.getInstancesByVirtualHostName(vipAddress);
} else {
return applications.getInstancesBySecureVirtualHostName(vipAddress);
}
}
4)服务下线(本质就是发送取消注册的HTTP请求到注册中心)
void unregister() {
// It can be null if shouldRegisterWithEureka == false
if(eurekaTransport != null && eurekaTransport.registrationClient != null) {
try {
logger.info("Unregistering ...");
// 重点在这里
EurekaHttpResponse httpResponse = eurekaTransport.registrationClient.cancel(instanceInfo.getAppName(), instanceInfo.getId());
logger.info(PREFIX + appPathIdentifier + " - deregister status: " + httpResponse.getStatusCode());
} catch (Exception e) {
logger.error(PREFIX + appPathIdentifier + " - de-registration failed" + e.getMessage(), e);
}
}
}
// AbstractJerseyEurekaHttpClient.cancel()
public EurekaHttpResponse cancel(String appName, String id) {
String urlPath = "apps/" + appName + '/' + id;
ClientResponse response = null;
try {
Builder resourceBuilder = jerseyClient.resource(serviceUrl).path(urlPath).getRequestBuilder();
addExtraHeaders(resourceBuilder);
// 本质就是发送delete请求到注册中心
response = resourceBuilder.delete(ClientResponse.class);
return anEurekaHttpResponse(response.getStatus()).headers(headersOf(response)).build();
} finally {
if (logger.isDebugEnabled()) {
logger.debug("Jersey HTTP DELETE {}/{}; statusCode={}", serviceUrl, urlPath, response == null ? "N/A" : response.getStatus());
}
if (response != null) {
response.close();
}
}
}
总结:通过以上分析可知,服务的注册、下线等操作实际上就是通过发送HTTP请求到注册中心来实现的。那么这些操作的执行时机是什么时候呢?是什么时候服务注册操作会被调用?下线操作是如何被触发的?这些请读者先自行思考下,下篇文章会来解密这些
参考:SpringCloud微服务实战