Eureka源码深度解析(上)

前言:

    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<EnableDiscoveryClient> {

    @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<String> 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<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()]);
	}


    // SpringFactoriesLoader.loadFactoryNames(this.annotationClass, this.beanClassLoader)
    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;
		}
        ...
	}

    注意: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<Void> httpResponse;
        try {
            // 主要的注册功能就是这句话
            // 真正实现在AbstractJerseyEurekaHttpClient.register()
            httpResponse = eurekaTransport.registrationClient.register(instanceInfo);
        }
        ...
        return httpResponse.getStatusCode() == 204;
    }

// AbstractJerseyEurekaHttpClient.register()
    public EurekaHttpResponse<Void> 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<InstanceInfo> 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<InstanceInfo> 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<InstanceInfo> 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<InstanceInfo> getInstancesByVipAddress(String vipAddress, boolean secure) {
        return getInstancesByVipAddress(vipAddress, secure, instanceRegionChecker.getLocalRegion());
    }

//getInstancesByVipAddress()
    public List<InstanceInfo> 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<Void> 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<Void> 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微服务实战

 

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

恐龙弟旺仔

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值