配置的同步涉及到兩個方面:第一,對原始的配置文件實施監控并在其發生變化之后從新加載配置;第二,配置重新加載之后及時通知應用程序進而使后者能夠使用最新的配置。要了解配置同步機制的實現原理,先得從認識一個名為ConfigurationReloadToken的類型開始。 [ 本文已經同步到《ASP.NET Core框架揭秘》之中]
目錄
一、從ConfigurationReloadToken說起
二、Configuration對象與配置文件的同步
三、應用重新加載的配置
四、同步流程總結
一、從ConfigurationReloadToken說起
.NET Core絕大部分的數據同步場景下都使用到一個名為ChangeToken的對象,該對象綁定到某個需要被監控的對象,并該對象發生改變是對外發送通知,我們可以注冊在被監控數據發生改變時可以自動執行的回調。在配置同步場景中,ConfigurationProvider會利用FileProvider監控配置文件的變化,并在變化時從新加載配置。ConfigurationReloadToken就是一個通知配置已經被重新加載的ChangeToken。
ConfigurationReloadToken本質上是對一個CancellationTokenSource對象的封裝。如果我們對.NET基于Task對象的并行/異步編程有所了解的話,相信對CancellationTokenSource應該不會感到模式。總的來說,我們可以利用CancellationTokenSource創建的CancellationToken向某個異步執行的Task發送“取消任務”的信號。如下面的代碼片段所示,ConfigurationReloadToken的HasChanged屬性對應的是這個CancellationTokenSource對象的IsCancellationRequested。通過調用RegisterChangeCallback方法注冊的回調實際上是注冊到 CancellationTokenSource創建的CancellationToken對象上。
1: public class ConfigurationReloadToken : IChangeToken
2: {
3: private CancellationTokenSource _cts = new CancellationTokenSource();
4:
5: public void OnReload()
6: {
7: _cts.Cancel();
8: }
9:
10: public IDisposable RegisterChangeCallback(Action<object> callback, object state)
11: {
12: return _cts.Token.Register(callback, state);
13: }
14:
15: public bool ActiveChangeCallbacks
16: {
17: get { return true; }
18: }
19:
20: public bool HasChanged
21: {
22: get { return _cts.IsCancellationRequested; }
23: }
24: }
當ConfigurationReloadToken的OnReload方法被執行的時候,這被封裝的CancellationTokenSource對象的Cancel方法隨之被調用。我們知道一旦這個Cancel方法被調用之后,CancellationTokenSource的IsCancellationRequested會馬上變成True,意味著ConfigurationReloadToken的HasChanged屬性也立即變成True。由于調用RegisterChangeCallback方法注冊的回調最是注冊到CancellationTokenSource創建的CancellationToken上的,所以該回調會在OnLoad方法被調用之后自動執行。
二、Configuration對象與配置文件的同步
在《聊聊默認支持的各種配置源》和《深入了解三種針對文件(JSON、XML與INI)的配置源》中,我們介紹了系統預定義的若干配置源,它們都通過相應的ConfigurationSource類型來表示,對于這些ConfigurationSource來說,只有針對配置文件的FileConfigurationSource才會涉及到配置同步的問題。其實這一點也可以由它們的定義看出來,因為只有FileConfigurationSource這個抽象類才定義了如下這個ReloadOnChange屬性來控制當配置文件改變之后是否需要重新加載配置。換句話說,配置的同步首先需要解決的是由ConfigurationBuilder創建的Configuration對象與原始配置文件的內容同步的問題,而解決這個問題的途徑就是對配置實施監控,并在文件發生改變之后自動重新加載配置。
1: public abstract class FileConfigurationSource : IConfigurationSource
2: {
3: ...
4: public bool ReloadOnChange { get; set; }
5: }
我們知道 FileConfigurationProvdier總是利用一個FileProvider對象來讀取對應的配置文件,除了讀取文件內容之外,FileProvider的Watch方法自身就提供了文件監控的能力。FileConfigurationProvdier利用FileProvider監控配置文件,并在配置文件發生改變時自動加載配置的操作實現在如下所示的代碼片段中。
1: public abstract class FileConfigurationProvider : ConfigurationProvider
2: {
3: ...
4: public FileConfigurationProvider(FileConfigurationSource source)
5: {
6: this.Source = source;
7: if (source.ReloadOnChange && (this.Source.FileProvider != null))
8: {
9: ChangeToken.OnChange(() => source.FileProvider.Watch(source.Path), this.Load);
10: }
11: }
12: }
三、應用重新加載的配置
Configuration對象與配置文件的同步問題解決之后,還需要讓應用程序感知到使用的Configuration對象已經發生改變,并且使之能夠將新的配置應用到程序之中。從編程的角度來講,這個問題很容易解決,我們只需要調用Configuration對象的GetReloadToeken方法得到一個ChangeToken對象,并將重新應用配置的操作注冊作為回調注冊到這個ChangeToken上面就可以了。
1: public interface IConfiguration
2: {
3: ...
4: IChangeToken GetReloadToken();
5: }
程序應用重新配置的回調是注冊到Configuration對象的GetReloadToken方法返回的ChangeToken對象上,而Configuration對象的重新加載最終是通過調用所有ConfigurationProvider的Load方法來實現的,所以兩者之間必然存在著某種聯系。說的具體一點,應用程序可以通過這個ChangeToken感知到配置系統針對ConfigurationProvider的Load方法的調用。要了解兩者之間的聯系,我們必須先弄清楚Configuration的 GetReloadToken方法返回的是怎樣一個ChangeToken對象。
一個Configuration對象代表配置樹的某個節點,對于組成同一棵配置樹的所有Configuration對象來說,它們的GetReloadToken方法返回的ChangeToken都來源于代表根節點的ConfigurationRoot對象。說的更加具體一點,當我們調用它們的GetReloadToken的時候,返回的其實是調用ConfigurationRoot的同名方法的返回值,那么我們有必要了解一下ConfigurationRoot的GetReloadToken方法的邏輯。
1: public class ConfigurationRoot : IConfigurationRoot
2: {
3: private ConfigurationReloadToken _changeToken = new ConfigurationReloadToken();
4: private IList<IConfigurationProvider> _providers;
5:
6: public ConfigurationRoot(IList<IConfigurationProvider> providers)
7: {
8: _providers = providers;
9:
10: foreach (var provider in providers)
11: {
12: provider.Load();
13: ChangeToken.OnChange(() => provider.GetReloadToken(), this.RaiseChanged);
14: }
15: }
16:
17: public IChangeToken GetReloadToken()
18: {
19: return _changeToken;
20: }
21:
22: private void RaiseChanged()
23: {
24: Interlocked.Exchange<ConfigurationReloadToken>(ref _changeToken, new ConfigurationReloadToken()).OnReload();
25: }
26:
27: public void Reload()
28: {
29: foreach (var provider in _providers)
30: {
31: provider.Load();
32: }
33: this.RaiseChanged();
34: }
35: }
如上面的代碼片段所示,ConfigurationRoot的GetReloadToken方法返回的是通過字段_changeToken表示的一個ConfigurationReloadToken對象。私有方法RaiseChanged通過調用ConfigurationReloadToken對象的OnReload向訂閱者發送配置重新被加載的通知,由于ChangeToken只能使用一次,所以該方法總是為_changeToken字段附上一個新的ConfigurationReloadToken對象。
針對這個RaiseChanged方法的調用發生在兩個地方,第一個地方發生在ConfigurationRoot的Reload方法上,也就是說當我們調用該方法以手工的方式重新加載配置的時候,注冊到Configuration對象提供的ChangeToken上的回調也會自動執行。
針對RaiseChanged方法的調用還出現在ConfigurationRoot構造函數中。如上面的代碼片段所示,ConfigurationRoot會調用每個ConfigurationProvdier的GetReloadToken方法,并將針對RaiseChanged方法的調用作為回調注冊到返回的ChangeToken上,也就是說注冊到Configuration對象提供的ChangeToken上的回調實際上注冊到ConfigurationProvider提供的ChangeToken上。既然如此,如果 ConfigurationProvider提供的這個ChangeToken能夠反映針對Load方法的調用,那么上面提到的關于Configuration提供的ChangeToken與ConfigurationProvider的Load方法之間的聯系就建立起來了。那么ConfigurationProvider的Load方法與ChangeToken方法返回的ChangeToken究竟有沒有關系呢?
1: public abstract class ConfigurationProvider : IConfigurationProvider
2: {
3: private ConfigurationReloadToken _reloadToken = new ConfigurationReloadToken();
4:
5: public IChangeToken GetReloadToken()
6: {
7: return_reloadToken;
8: }
9:
10: protected void OnReload()
11: {
12: Interlocked.Exchange<ConfigurationReloadToken>(ref_reloadToken, new ConfigurationReloadToken()).OnReload();
13: }
14: }
如上面的代碼片段所示,抽象類ConfigurationProvider的GetRealoadToken方法返回的是一個通過字段_reloadToken表示的ConfigurationReloadToken對象。該類型還定義了一個受保護的OnReload方法,該方法具有與上面介紹的RaiseChanged方法一樣的邏輯,意味著ConfigurationProvider實際上是調用這個方法對外發送配置被重新加載的通知。針對這個OnLoad方法的調用發生在FileConfigurationProvider的Load方法中。所以上面提到的讓ConfigurationProvider提供的ChangeToken能夠反映針對Load方法的調用最終實現在FileConfigurationProvider中。
1: public abstract class FileConfigurationProvider : ConfigurationProvider
2: {
3: ...
4: public override void Load()
5: {
6: ...
7: base.OnReload();
8: }
9: }
四、同步流程總結
上面我們通過代碼分析的方式捋清了配置文件在發生改變的時候為什么會導致配置的重新加載,注冊到Configuration通過GetRealoadToken方法提供的ChangeToken上的回調為什么會自動執行。可能都有讀者的腦子里面還是比較暈,所以我們利用如下所示的序列圖繼續對這個過程進行講解。用于讀取配置文件內容的FileConfigurationProvder會調用FileProvder的Watch方法來監控文件的變化(實際上真正用于文件監控的實PhysicalFileProvider所示用的FileSystemWatcher),并且通過向返回的ChangeToken注冊回調的方式來調用自身的Load方法來實現配置配置的重新加載。
當Load方法執行的時候,它會在配置加載完成之后調用調用Reload方法,后者利用一個ConfigurationReloadToken對象對外發出配置被重新加載的通知,最終會觸發注冊到Configuration對象上的回調的執行。注冊到Configuration對象上的回調出了可以在配置被改動的時候自動觸發之外,我們還可以直接調用ConfigurationRoot的Reload方法來觸發它。
.NET Core采用的全新配置系統[1]: 讀取配置數據
.NET Core采用的全新配置系統[2]: 配置模型設計詳解
.NET Core采用的全新配置系統[3]: Options模式”下的配置是如何綁定為Options對象
.NET Core采用的全新配置系統[4]: Options模式”下各種類型的Options對象是如何綁定的?
.NET Core采用的全新配置系統[5]: 聊聊默認支持的各種配置源[內存變量,環境變量和命令行參數]
.NET Core采用的全新配置系統[6]: 深入了解三種針對文件(JSON、XML與INI)的配置源
.NET Core采用的全新配置系統[7]: 將配置保存在數據庫中
.NET Core采用的全新配置系統[8]: 如何實現配置與源文件的同步
.NET Core采用的全新配置系統[9]: 為什么針對XML的支持不夠好?如何改進?
.NET Core采用的全新配置系統[10]: 配置的同步機制是如何實現的?
文章列表