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微服務實戰

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章