您好,登錄后才能下訂單哦!
這篇文章將為大家詳細講解有關(guān)微服務(wù)中如何進行Eureka配置部分源碼分析,文章內(nèi)容質(zhì)量較高,因此小編分享給大家做個參考,希望大家閱讀完這篇文章后對相關(guān)知識有一定的了解。
今天,我們開始來研究 Eureka 的源碼,先從配置部分的源碼開始看,其他部分后面再補充。
補充一點,我更多地會從設(shè)計層面分析源碼,而不會順序地剖析每個過程的代碼。一方面是因為篇幅有限,另一方面是因為我認為這樣做更有意義一些。
os:win 10
jdk:1.8.0_231
eureka:1.10.11
maven:3.6.3
ConcurrentCompositeConfiguration 這個類是 Eureka 配置體系的核心 。在這個例子中,我們使用它 對 property 進行增刪改查 ,并 注冊了自定義監(jiān)聽器來監(jiān)聽 property 的改變 。
@Test public void test01() { // 創(chuàng)建配置對象 final ConcurrentCompositeConfiguration config = new ConcurrentCompositeConfiguration(); // 注冊監(jiān)聽器監(jiān)聽property的改變 config.addConfigurationListener(new ConfigurationListener() { public void configurationChanged(ConfigurationEvent event) { // 增加property if(AbstractConfiguration.EVENT_ADD_PROPERTY == event.getType() && !event.isBeforeUpdate()) { System.err.println("add property:" + event.getPropertyName() + "=" + event.getPropertyValue()); return; } // 刪除property if(AbstractConfiguration.EVENT_CLEAR_PROPERTY == event.getType()) { System.err.println("clear property:" + event.getPropertyName()); return; } // 更新property if(AbstractConfiguration.EVENT_SET_PROPERTY == event.getType() && event.isBeforeUpdate() && !config.getString(event.getPropertyName()).equals(event.getPropertyValue())) { System.err.println("update property:" + event.getPropertyName() + ":" + config.getString(event.getPropertyName()) + "==>" + event.getPropertyValue() ); return; } } }); // 添加property config.addProperty("author", "zzs"); // 獲取property System.err.println(config.getString("author")); // 更改property config.setProperty("author", "zzf"); // 刪除property config.clearProperty("author"); } // 運行以上方法,控制臺打印內(nèi)容: // add property:author=zzs // zzs // update property:author:zzs==>zzf // clear property:author
可以看到,當(dāng)我們更改了 property 時,監(jiān)聽器中的方法被觸發(fā)了,利用這一點,我們可以實現(xiàn)動態(tài)配置。
后面就會發(fā)現(xiàn), Eureka 底層使用 ConcurrentCompositeConfiguration 來對配置參數(shù)進行增刪改查,并基于事件監(jiān)聽的機制來支持動態(tài)配置 。
我們再來看看一個 UML 圖。上面例子中說到 ConcurrentCompositeConfiguration 的兩個功能,是通過實現(xiàn) Configuration 和繼承 EventSource 來獲得的,這一點沒什么特別的,之所以深究它,是因為我發(fā)現(xiàn)了其他有趣的地方。
我們主要來關(guān)注下它的三個成員屬性(它們都是 AbstractConfiguration 類型):
configList :持有的配置對象集合。 這個集合的配置對象存在優(yōu)先級 ,舉個例子,如果我添加了 Configuration1 和 Configuration2,當(dāng)我們 getProperty(String) 時,會優(yōu)先從 Configuration1 獲取,實在找不到才會去 Configuration2 獲取。
overrideProperties : 最高優(yōu)先級的配置對象 。當(dāng)我們 getProperty(String) 時,會先從這里獲取,實在沒有才會去 configList 里找。
containerConfiguration : 保底的配置對象 。一般是 configList 的最后一個(注意,不一定是最后一個1),我們往 ConcurrentCompositeConfiguration 里增刪改 property,實際操作的就是這個對象。
為了更好理解它們的作用,我寫了個測試例子。
@Test public void test02() { // 創(chuàng)建配置對象 ConcurrentCompositeConfiguration config = new ConcurrentCompositeConfiguration(); // 添加配置1 ConcurrentMapConfiguration config1 = new ConcurrentMapConfiguration(); config1.addProperty("author", "zzs"); config.addConfiguration(config1, "CONFIG_01"); // 添加配置2 ConcurrentMapConfiguration config2 = new ConcurrentMapConfiguration(); config2.addProperty("author", "zzf"); config.addConfiguration(config2, "CONFIG_02"); // 在默認的containerConfiguration中添加property config.addProperty("author", "zhw"); // ============以下測試configList的優(yōu)先級============ System.err.println(config.getString("author")); // 刪除config1中的property config1.clearProperty("author"); System.err.println(config.getString("author")); // 刪除config2中的property config2.clearProperty("author"); System.err.println(config.getString("author")); // ============以下測試overrideProperties的優(yōu)先級============ // 添加overrideProperties的property config.setOverrideProperty("author", "lt"); System.err.println(config.getString("author")); } // 運行以上方法,控制臺打印內(nèi)容: // zzs // zzf // zhw // lt
這里補充一點,當(dāng)我們創(chuàng)建 ConcurrentCompositeConfiguration 時,就會生成一個 containerConfiguration,默認情況下,它會一直在集合最后面,每次添加新的配置對象,都是往 containerConfiguration 前面插入。
通過上面的例子可以知道, ConcurrentCompositeConfiguration 并不會主動地去加載配置,所以,Eureka 需要自己往 ConcurrentCompositeConfiguration 里添加配置,而完成這件事的是另外一個類-- ConfigurationManager 。
ConfigurationManager 作為一個單例對象使用,用來初始化配置對象,以及提供加載配置文件的方法 (后面的 DefaultEurekaClientConfig 、 DefaultEurekaServerConfig 會來調(diào)用這些方法)。
下面我們看看配置對象的初始化。在 ConfigurationManager 被加載時就會初始化配置對象,進入到它的靜態(tài)代碼塊就可以找到。我截取的是最關(guān)鍵部分的代碼。
private static AbstractConfiguration createDefaultConfigInstance() { ConcurrentCompositeConfiguration config = new ConcurrentCompositeConfiguration(); try { // 加載指定url的配置 // 通過archaius.configurationSource.additionalUrls啟動參數(shù)設(shè)置url,多個逗號隔開 DynamicURLConfiguration defaultURLConfig = new DynamicURLConfiguration(); config.addConfiguration(defaultURLConfig, URL_CONFIG_NAME); } catch (Throwable e) { logger.warn("Failed to create default dynamic configuration", e); } if (!Boolean.getBoolean(DISABLE_DEFAULT_SYS_CONFIG)) { // 加載System.getProperties()的配置 // 通過archaius.dynamicProperty.disableSystemConfig啟動參數(shù)可以控制是否添加 SystemConfiguration sysConfig = new SystemConfiguration(); config.addConfiguration(sysConfig, SYS_CONFIG_NAME); } if (!Boolean.getBoolean(DISABLE_DEFAULT_ENV_CONFIG)) { // 加載System.getenv()的配置 // 通過archaius.dynamicProperty.disableEnvironmentConfig啟動參數(shù)可以控制是否添加 EnvironmentConfiguration envConfig = new EnvironmentConfiguration(); config.addConfiguration(envConfig, ENV_CONFIG_NAME); } // 這個是自定義的保底配置 ConcurrentCompositeConfiguration appOverrideConfig = new ConcurrentCompositeConfiguration(); config.addConfiguration(appOverrideConfig, APPLICATION_PROPERTIES); config.setContainerConfigurationIndex(config.getIndexOfConfiguration(appOverrideConfig));// 這里可以更改保底配置 return config; }
可以看到, Eureka 支持通過 url 來指定配置文件,只要指定啟動參數(shù)就行 ,這一點將有利于我們更靈活地對項目進行配置。默認情況下,它還會去加載所有的系統(tǒng)參數(shù)和環(huán)境參數(shù)。
另外,當(dāng)我們設(shè)置以下啟動參數(shù),就可以通過 JMX 的方式來更改配置。
-Darchaius.dynamicPropertyFactory.registerConfigWithJMX=true
配置對象初始化后, ConfigurationManager 提供了方法供我們加載配置文件(本地或遠程),如下。
// 這兩個的區(qū)別在于:前者會生成一個新的配置添加到configList;后者直接將property都加入到appOverrideConfig public static void loadCascadedPropertiesFromResources(String configName) throws IOException; public static void loadAppOverrideProperties(String appConfigName);
動態(tài)配置的內(nèi)容直接看源碼不大好理解,我們先通過一個再簡單不過的例子開始來一步步實現(xiàn)我們自己的動態(tài)配置。在下面的方法中,我更改了 property,但是拿不到更新的值。原因嘛,我相信大家都知道。
@Test public void test03() { // 獲取配置對象 AbstractConfiguration config = ConfigurationManager.getConfigInstance(); // 添加一個property config.addProperty("author", "zzs"); String author = config.getString("author", ""); System.err.println(author); // 更改property config.setProperty("author", "zzf"); System.err.println(author); } // 運行以上方法,控制臺打印內(nèi)容: // zzs // zzs
為了拿到更新的值,我把代碼改成這樣。我不定義變量來存放 property 的值,每次都重新獲取。顯然,這樣做可以成功。
@Test public void test04() { // 獲取配置對象 AbstractConfiguration config = ConfigurationManager.getConfigInstance(); // 添加一個property config.addProperty("author", "zzs"); System.err.println(config.getString("author", "")); // 更改property config.setProperty("author", "zzf"); System.err.println(config.getString("author", "")); } // 運行以上方法,控制臺打印內(nèi)容: // zzs // zzf
但是上面的做法有個問題,我們都知道從 ConcurrentCompositeConfiguration 中獲取 property 是比較麻煩的,因為我需要去遍歷 configList,以及進行參數(shù)的轉(zhuǎn)換等。每次都這樣拿,不大合理。
于是,我增加了緩存來減少這部分的開銷,當(dāng)然,property 更改時我必須刷新緩存。
@Test public void test05() { // 緩存 Map<String, String> cache = new ConcurrentHashMap<String, String>(); // 獲取配置對象 AbstractConfiguration config = ConfigurationManager.getConfigInstance(); // 添加一個property config.addProperty("author", "zzs"); String value = cache.computeIfAbsent("author", x -> config.getString(x, "")); System.err.println(value); // 添加監(jiān)聽器監(jiān)聽property的更改 config.addConfigurationListener(new ConfigurationListener() { public void configurationChanged(ConfigurationEvent event) { // 刪除property if(AbstractConfiguration.EVENT_CLEAR_PROPERTY == event.getType()) { cache.remove(event.getPropertyName()); return; } // 更新property if(AbstractConfiguration.EVENT_SET_PROPERTY == event.getType() && !event.isBeforeUpdate()) { cache.put(event.getPropertyName(), String.valueOf(event.getPropertyValue())); return; } } }); // 更改property config.setProperty("author", "zzf"); System.err.println(cache.get("author")); } // 運行以上方法,控制臺打印內(nèi)容: // zzs // zzf
通過上面的例子,我們實現(xiàn)了動態(tài)配置。
現(xiàn)在我們再來看看 Eureka 是怎么實現(xiàn)的。這里用到了 DynamicPropertyFactory 和 DynamicStringProperty 兩個類,通過它們,也實現(xiàn)了動態(tài)配置。
@Test public void test06() { // 獲取配置對象 AbstractConfiguration config = ConfigurationManager.getConfigInstance(); // 添加一個property config.addProperty("author", "zzs"); // 通過DynamicPropertyFactory獲取property DynamicPropertyFactory dynamicPropertyFactory = DynamicPropertyFactory.getInstance(); DynamicStringProperty stringProperty = dynamicPropertyFactory.getStringProperty("author", ""); System.err.println(stringProperty.get()); // 更改property config.setProperty("author", "zzf"); System.err.println(stringProperty.get()); } // 運行以上方法,控制臺打印內(nèi)容: // zzs // zzf
至于原理,其實和我們上面的例子是差不多的。通過 UML 圖可以知道, DynamicProperty 中就放了一張緩存表,每次獲取 property 時,會優(yōu)先從這里拿。
既然有緩存,就應(yīng)該有監(jiān)聽器,沒錯,在 DynamicProperty.initialize(DynamicPropertySupport) 方法中就可以看到。
static synchronized void initialize(DynamicPropertySupport config) { dynamicPropertySupportImpl = config; // 注冊監(jiān)聽器 config.addConfigurationListener(new DynamicPropertyListener()); updateAllProperties(); }
在上面的分析中,我們用 ConfigurationManager 來初始化配置對象,并使用 DynamicPropertyFactory 來實現(xiàn)動態(tài)配置,這些東西構(gòu)成了 Eureka 的配置體系的基礎(chǔ),比較通用。基礎(chǔ)之上,是 Eureka 更具體的一些配置對象。
在 Eureka 里,配置分成了三種(理解這一點非常重要):
EurekaInstanceConfig :當(dāng)前實例身份的配置信息,即 我是誰?
EurekaServerConfig :一些影響當(dāng)前Eureka Server和客戶端或?qū)Φ裙?jié)點交互行為的配置信息,即 怎么交互?
EurekaClientConfig :一些影響當(dāng)前實例和Eureka Server交互行為的配置信息,即 和誰交互?怎么交互?
這三個對象都持有了 DynamicPropertyFactory 的引用,所以支持動態(tài)配置,另外,它們還是用 ConfigurationManager 來加載自己想要的配置文件。例如, EurekaInstanceConfig 、 EurekaClientConfig 負責(zé)加載 eureka-client.properties ,而 EurekaServerConfig 則負責(zé)加載 eureka-server.properties 。
關(guān)于微服務(wù)中如何進行Eureka配置部分源碼分析就分享到這里了,希望以上內(nèi)容可以對大家有一定的幫助,可以學(xué)到更多知識。如果覺得文章不錯,可以把它分享出去讓更多的人看到。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。