Eureka源碼深度解析(1)

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之間的服務同步。

服務續約 Renew

在註冊完成成後,Eureka客戶會每隔30秒發送一次心跳來續約。 通過續約來告知Eureka Server該Eureka客戶仍然存在,沒有出現問題。 正常情況下,如果Eureka Server在90秒沒有收到Eureka客戶的續約,它會將實例從其註冊表中刪除。 建議不要更改續約間隔。

服務下線 Cancel

當服務實例正常關閉時,它會發送一個服務下線的消息給註冊中心,註冊中心收到信息後,會將該服務實例狀態置爲下線,並把該信息傳播出去。該下線請求不會自動完成,它需要調用以下內容:DiscoveryManager.getInstance().shutdownComponent()

獲取註冊列表信息  Fetch Registries

Eureka客戶端從服務器獲取註冊表信息,並將其緩存在本地。客戶端會使用該信息查找其他服務,從而進行遠程調用。該註冊列表信息定期(每30秒鐘)更新一次。每次返回註冊列表信息可能與Eureka客戶端的緩存信息不同, Eureka客戶端自動處理。如果由於某種原因導致註冊列表信息不能及時匹配,Eureka客戶端則會重新獲取整個註冊表信息。 Eureka服務器緩存註冊列表信息,整個註冊表以及每個應用程序的信息進行了壓縮,壓縮內容和沒有壓縮的內容完全相同。Eureka客戶端和Eureka 服務器可以使用JSON / XML格式進行通訊。在默認的情況下Eureka客戶端使用壓縮JSON格式來獲取註冊列表的信息。

失效剔除 Eviction

有時候,服務實例可能無法正常提供服務,而註冊中心沒有收到服務下線的信息。註冊中心會創建一個定時任務,將超過一定時間沒有服務續約消息的服務實例從服務清單中剔除。

自我保護

上面講到失效剔除時,會將超過一定時間沒有收到服務續約消息的實例從服務清單中剔除掉,在這中間還有一個邏輯。如果在運行期間,統計心跳成功的比例低於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的詳細解析請見下文。

 

最後推薦一篇詳解博客:

https://github.com/xusuy/SpringCloudTutorial/blob/9d6807e5653328b72bf7a44cb50453cb836aa94d/doc/flow-analysis/Eureka_01.md
 

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