您好,登錄后才能下訂單哦!
這篇文章將為大家詳細講解有關微服務中如何進行Eureka配置部分源碼分析,文章內容質量較高,因此小編分享給大家做個參考,希望大家閱讀完這篇文章后對相關知識有一定的了解。
今天,我們開始來研究 Eureka 的源碼,先從配置部分的源碼開始看,其他部分后面再補充。
補充一點,我更多地會從設計層面分析源碼,而不會順序地剖析每個過程的代碼。一方面是因為篇幅有限,另一方面是因為我認為這樣做更有意義一些。
os:win 10
jdk:1.8.0_231
eureka:1.10.11
maven:3.6.3
ConcurrentCompositeConfiguration 這個類是 Eureka 配置體系的核心 。在這個例子中,我們使用它 對 property 進行增刪改查 ,并 注冊了自定義監聽器來監聽 property 的改變 。
@Test public void test01() { // 創建配置對象 final ConcurrentCompositeConfiguration config = new ConcurrentCompositeConfiguration(); // 注冊監聽器監聽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"); } // 運行以上方法,控制臺打印內容: // add property:author=zzs // zzs // update property:author:zzs==>zzf // clear property:author
可以看到,當我們更改了 property 時,監聽器中的方法被觸發了,利用這一點,我們可以實現動態配置。
后面就會發現, Eureka 底層使用 ConcurrentCompositeConfiguration 來對配置參數進行增刪改查,并基于事件監聽的機制來支持動態配置 。
我們再來看看一個 UML 圖。上面例子中說到 ConcurrentCompositeConfiguration 的兩個功能,是通過實現 Configuration 和繼承 EventSource 來獲得的,這一點沒什么特別的,之所以深究它,是因為我發現了其他有趣的地方。
我們主要來關注下它的三個成員屬性(它們都是 AbstractConfiguration 類型):
configList :持有的配置對象集合。 這個集合的配置對象存在優先級 ,舉個例子,如果我添加了 Configuration1 和 Configuration2,當我們 getProperty(String) 時,會優先從 Configuration1 獲取,實在找不到才會去 Configuration2 獲取。
overrideProperties : 最高優先級的配置對象 。當我們 getProperty(String) 時,會先從這里獲取,實在沒有才會去 configList 里找。
containerConfiguration : 保底的配置對象 。一般是 configList 的最后一個(注意,不一定是最后一個1),我們往 ConcurrentCompositeConfiguration 里增刪改 property,實際操作的就是這個對象。
為了更好理解它們的作用,我寫了個測試例子。
@Test public void test02() { // 創建配置對象 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的優先級============ 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的優先級============ // 添加overrideProperties的property config.setOverrideProperty("author", "lt"); System.err.println(config.getString("author")); } // 運行以上方法,控制臺打印內容: // zzs // zzf // zhw // lt
這里補充一點,當我們創建 ConcurrentCompositeConfiguration 時,就會生成一個 containerConfiguration,默認情況下,它會一直在集合最后面,每次添加新的配置對象,都是往 containerConfiguration 前面插入。
通過上面的例子可以知道, ConcurrentCompositeConfiguration 并不會主動地去加載配置,所以,Eureka 需要自己往 ConcurrentCompositeConfiguration 里添加配置,而完成這件事的是另外一個類-- ConfigurationManager 。
ConfigurationManager 作為一個單例對象使用,用來初始化配置對象,以及提供加載配置文件的方法 (后面的 DefaultEurekaClientConfig 、 DefaultEurekaServerConfig 會來調用這些方法)。
下面我們看看配置對象的初始化。在 ConfigurationManager 被加載時就會初始化配置對象,進入到它的靜態代碼塊就可以找到。我截取的是最關鍵部分的代碼。
private static AbstractConfiguration createDefaultConfigInstance() { ConcurrentCompositeConfiguration config = new ConcurrentCompositeConfiguration(); try { // 加載指定url的配置 // 通過archaius.configurationSource.additionalUrls啟動參數設置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啟動參數可以控制是否添加 SystemConfiguration sysConfig = new SystemConfiguration(); config.addConfiguration(sysConfig, SYS_CONFIG_NAME); } if (!Boolean.getBoolean(DISABLE_DEFAULT_ENV_CONFIG)) { // 加載System.getenv()的配置 // 通過archaius.dynamicProperty.disableEnvironmentConfig啟動參數可以控制是否添加 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 來指定配置文件,只要指定啟動參數就行 ,這一點將有利于我們更靈活地對項目進行配置。默認情況下,它還會去加載所有的系統參數和環境參數。
另外,當我們設置以下啟動參數,就可以通過 JMX 的方式來更改配置。
-Darchaius.dynamicPropertyFactory.registerConfigWithJMX=true
配置對象初始化后, ConfigurationManager 提供了方法供我們加載配置文件(本地或遠程),如下。
// 這兩個的區別在于:前者會生成一個新的配置添加到configList;后者直接將property都加入到appOverrideConfig public static void loadCascadedPropertiesFromResources(String configName) throws IOException; public static void loadAppOverrideProperties(String appConfigName);
動態配置的內容直接看源碼不大好理解,我們先通過一個再簡單不過的例子開始來一步步實現我們自己的動態配置。在下面的方法中,我更改了 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); } // 運行以上方法,控制臺打印內容: // 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", "")); } // 運行以上方法,控制臺打印內容: // zzs // zzf
但是上面的做法有個問題,我們都知道從 ConcurrentCompositeConfiguration 中獲取 property 是比較麻煩的,因為我需要去遍歷 configList,以及進行參數的轉換等。每次都這樣拿,不大合理。
于是,我增加了緩存來減少這部分的開銷,當然,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); // 添加監聽器監聽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")); } // 運行以上方法,控制臺打印內容: // zzs // zzf
通過上面的例子,我們實現了動態配置。
現在我們再來看看 Eureka 是怎么實現的。這里用到了 DynamicPropertyFactory 和 DynamicStringProperty 兩個類,通過它們,也實現了動態配置。
@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()); } // 運行以上方法,控制臺打印內容: // zzs // zzf
至于原理,其實和我們上面的例子是差不多的。通過 UML 圖可以知道, DynamicProperty 中就放了一張緩存表,每次獲取 property 時,會優先從這里拿。
既然有緩存,就應該有監聽器,沒錯,在 DynamicProperty.initialize(DynamicPropertySupport) 方法中就可以看到。
static synchronized void initialize(DynamicPropertySupport config) { dynamicPropertySupportImpl = config; // 注冊監聽器 config.addConfigurationListener(new DynamicPropertyListener()); updateAllProperties(); }
在上面的分析中,我們用 ConfigurationManager 來初始化配置對象,并使用 DynamicPropertyFactory 來實現動態配置,這些東西構成了 Eureka 的配置體系的基礎,比較通用。基礎之上,是 Eureka 更具體的一些配置對象。
在 Eureka 里,配置分成了三種(理解這一點非常重要):
EurekaInstanceConfig :當前實例身份的配置信息,即 我是誰?
EurekaServerConfig :一些影響當前Eureka Server和客戶端或對等節點交互行為的配置信息,即 怎么交互?
EurekaClientConfig :一些影響當前實例和Eureka Server交互行為的配置信息,即 和誰交互?怎么交互?
這三個對象都持有了 DynamicPropertyFactory 的引用,所以支持動態配置,另外,它們還是用 ConfigurationManager 來加載自己想要的配置文件。例如, EurekaInstanceConfig 、 EurekaClientConfig 負責加載 eureka-client.properties ,而 EurekaServerConfig 則負責加載 eureka-server.properties 。
關于微服務中如何進行Eureka配置部分源碼分析就分享到這里了,希望以上內容可以對大家有一定的幫助,可以學到更多知識。如果覺得文章不錯,可以把它分享出去讓更多的人看到。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。