文章出處

本節所謂的“配置同步”主要體現在兩個方面:其一,如何監控配置源并在其變化的時候自動加載其數據,其目的是讓應用中通過Configuration對象承載的配置與配置源的數據同步;其二、當Configuration對象承載的配置放生變換的時候如何向應用程序發送通知,最終讓應用程序使用最新的配置。

一、配置與配置源的同步

配置模型提供了三個原生ConfigurationProvider(JsonConfigrationProvider、XmlConfigurationProvider和IniConfigurationProvider)使我們可以將三種格式(JSON、XML和INI)的文件作為配置原始數據的來源,所以針對物理文件的配置同步是配置同步機制的一個主要的應用領域。在上面演示的實例中,基于物理文件的同步是通過調用ConfigurationRoot的擴展方法ReloadOnChanged來實現的。

這個擴展方法定義在NuGet包“Microsoft.Extensions.Configuration.FileProviderExtensions”之中,除了在我們演示的實例中使用的那個方法之外,這個ReloadOnChanged方法還具有如下兩個額外的重載。對于這三個ReloadOnChanged方法重載來說,最終的實現均落在第三個重載上。至于最本質的物理文件監控的功能則由一個名為FileProvider的對象負責。

   1: public static class FileProviderExtensions
   2: {
   3:     public static IConfigurationRoot ReloadOnChanged(
   4:         this IConfigurationRoot config, string filename);
   5:  
   6:     public static IConfigurationRoot ReloadOnChanged(
   7:         this IConfigurationRoot config, string basePath, string filename);
   8:  
   9:     public static IConfigurationRoot ReloadOnChanged(this IConfigurationRoot config, 
  10:         IFileProvider fileProvider, string filename);
  11: }

這里所謂的FileProvider是對所有實現了IFileProvider接口的類型及其對象的統稱。IFileProvier接口定義在命名空間“Microsoft.AspNet.FileProviders”下,它通過定義其中的方法提供抽象化的目錄與文件信息,針對文件監控相關的方法也定義在這個接口下。如下面的代碼片段所示,IFileProvier具有三個方法,其中GetDirectoryContents和GetFileInfo用于提供目錄和文件的相關信息,我們只需要關注旨在監控文件變化的Watch方法。

   1: public interface IFileProvider
   2: {
   3:     IDirectoryContents GetDirectoryContents(string subpath);
   4:     IFileInfo GetFileInfo(string subpath);
   5:     IChangeToken Watch(string filter);
   6: }

一個FileProvider總是針對一個具體的目錄,Watch方法的參數filter旨在幫助篩選出需要監控的文件。這個參數是一個可以攜帶通配符(“*”)的字符串,比如 “ *.*”則表示所有文件,而“ *.json”則表示所有擴展名為“ .json”的文件。如果我們需要監控當前目錄下某個確定的文件,直接將文件名作為參數即可。Watch方法的返回類型為具有如下定義的IChangeToken接口,我們可以將它理解為一個用于傳遞數據變換通知的令牌。

   1: public interface IChangeToken
   2: {
   3:     bool HasChanged { get; }
   4:     bool ActiveChangeCallbacks { get; }
   5:     
   6:     IDisposable RegisterChangeCallback(Action<object> callback, object state);    
   7: }

IChangeToken的只讀屬性HasChanged表示目標數據是否發生改變。我們可以通過調用它的RegisterChangeCallback方法注冊一個在數據發生變化時需要執行的回調操作。該方法返回的對象對應的類型必須實現IDisposable接口,回調注冊的接觸可以通過Dispose方法來完成。至于IChangeToken接口的另個只讀屬性ActiveChangeCallbacks表示當數據發生變化時是否需要主動執行注冊的回調操作。實際上IConfiguration的GetReloadToke方法的返回類型就是這么一個接口,至于該方法具體返回一個怎樣的對象,我們會在下一節予以介紹。

當我們指定一個具體的FileProvider對象調用ConfigurationRoot的擴展方法ReloadOnChanged時,后者會調用這個FileProvider的RegisterChangeCallback方法以注冊一個在指定文件發生變化時的回調。至于這個注冊的回調,它會調用ConfigurationRoot的Reload方法實現對配置數據的重新加載。由于注冊了這樣一個回調,該方法只需要調用FileProvider的Watch方法監控指定文件的變化即可,如下所示的代碼片段基本上體現了ReloadOnChanged方法的邏輯。

   1: public static IConfigurationRoot ReloadOnChanged(
   2:     this IConfigurationRoot config, IFileProvider fileProvider, string filename)
   3: {
   4:     Action<object> callback = null;
   5:     callback = _ =>
   6:     {
   7:         config.Reload();
   8:         fileProvider.Watch(filename).RegisterChangeCallback(callback, null);
   9:     };
  10:     fileProvider.Watch(filename).RegisterChangeCallback(callback, null);
  11:     return config;
  12: }

如果我們通過指定目錄和文件名調用另一個ReloadOnChanged方法重載,后者會根據指定的目錄創建一個PhysicalFileProvider對象并作為參數調用上面這個重載。顧名思義,PhysicalFileProvider是一個針對具體物理文件的FileProvider,它實際上是借助一個FileSystemWatcher對象來監控指定的文件。這個ReloadOnChanged方法的實現邏輯體現在如下所示的代碼片段中。當我們僅僅指定監控文件名調用第一個ReloadOnChanged方法重載時,該方法會將當前應用所在的目錄作為參數調用上面一個重載。

   1: public static class FileProviderExtensions
   2: {
   3:    public static IConfigurationRoot ReloadOnChanged(
   4:        this IConfigurationRoot config, string basePath, string filename) 
   5:        => config.ReloadOnChanged(new PhysicalFileProvider(basePath), filename);
   6:     //其他成員
   7: }


二、應用重新加載的配置

ConfigurationRoot通過擴展方法ReloadOnChanged方法與一個具體的物理文件綁定在一起,針對該文件的任何修改操作都會促使Reload方法的調用,進而保證自身承載的數據總是與配置源保持同步。現在我們來討論配置同步的另一個話題,即如何在不重啟應用程序的情況下使用新的配置。要了解這個問題的解決方案,我們得先來聊聊定義在IConfiguration接口中這個一直刻意回避的方法GetReloadToken。

   1: public interface IConfiguration
   2: {
   3:     //其他成員
   4:     IChangeToken GetReloadToken();
   5: }

如上面的代碼片段所示,這個GetReloadToken方法的返回類型為上面討論過的IChangeToken接口,我們說可以將后者視為一個傳遞數據變化信息的令牌。對于一個Configuration對象來說,它所謂的數據變換體現作為配置根節點的ConfigurationRoot對象的重新加載,所以這個方法返回的ChangeToken對象體現了最近一次加載引起的配置變化。

   1: public class ConfigurationReloadToken : IChangeToken
   2: {
   3:     public void OnReload();
   4:     public IDisposable RegisterChangeCallback(Action<object> callback, 
   5:         object state);
   6:   
   7:     public bool ActiveChangeCallbacks { get; }
   8:     public bool HasChanged { get; }
   9: }

對于實現了IConfiguration接口的兩個默認類型(ConfigurationRoot和ConfigurationSection)來說,它們的GetReloadToken方法返回的是一個ConfigurationReloadToken對象。如上面的代碼片段所示,除了實現定義在IConfiguration接口中的所有成員之外,ConfigurationReloadToken還具有另一個名為OnReload的方法。當配置數據發生變化,也就是調用通過ConfigurationRoot的Reload方法重新加載配置的時候,這個方法會被調用用以發送“配置已經發生變化”的信號。

實現在ConfigurationReloadToken之中用于傳遞配置變化的邏輯其實很簡單,具體的邏輯是借助于一個CancellationTokenSource對象來完成。如果讀者朋友們了解針對Task的異步編程,相信對這個類型不會感到陌生。總的來說,我們可以利用CancellationTokenSource向某個異步執行的Task發送“取消任務”的信號。

   1: public class ConfigurationReloadToken : IChangeToken
   2: {
   3:     private CancellationTokenSource tokenSource = new CancellationTokenSource();
   4:  
   5:     public void OnReload() => tokenSource.Cancel();
   6:     public IDisposable RegisterChangeCallback(Action<object> callback, object state) 
   7:         => tokenSource.Token.Register(callback, state);
   8:  
   9:     public bool ActiveChangeCallbacks { get; } = true;
  10:     public bool HasChanged
  11:     {
  12:         get { return tokenSource.IsCancellationRequested; }
  13:     }
  14: }

如上面的代碼片段所示,ConfigurationReloadToken本質上就是一個CancellationTokenSource對象的封裝。當OnReload方法被調用的時候,它直接調用CancellationTokenSource的Cancel方法發送取消任務的請求,而HasChanged屬性則通過CancellationTokenSource的IsCancellationRequested屬性通過判斷任務取消請求是否發出來判斷配置數據是否發生變化。通過RegisterChangeCallback注冊的回調最終注冊到由CancellationTokenSource創建的CancellationToken對象上,所以一旦OnReload方法被調用,注冊的回調會自動執行。ConfigurationReloadToken的ActiveChangeCallbacks屬性總是返回True。

ConfigurationRoot和ConfigurationSection這兩個類型分別采用如下的形式實現了GetReloadToken方法。我們從給出的代碼片段不難看出所有的ConfigurationSection對象和作為它們根的ConfigurationRoot對象來說,它們的GetReloadToken方法在同一時刻返回的是同一個ConfigurationReloadToken對象。當ConfigurationRoot的Reload方法被調用的時候,當前ConfigurationReloadToken對象的OnReload方法會被調用,在此之后一個新的ConfigurationReloadToken對象會被創建出來并代替原來的對象。

   1: public class ConfigurationRoot : IConfigurationRoot
   2: {
   3:     private ConfigurationReloadToken reloadToken = new ConfigurationReloadToken();
   4:  
   5:     public IChangeToken GetReloadToken()
   6:     {
   7:         return reloadToken;
   8:     }
   9:  
  10:     public void Reload()
  11:     {
  12:         //省略重新加載配置代碼
  13:         Interlocked.Exchange<ConfigurationReloadToken>(ref this._reloadToken, 
  14:             new ConfigurationReloadToken()).OnReload();
  15:     }
  16:     //其他成員
  17: }
  18:  
  19: public class ConfigurationSection : IConfigurationSection, IConfiguration
  20: {
  21:     private readonly ConfigurationRoot root;
  22:     public IChangeToken GetReloadToken()
  23:     {
  24:         return root.GetReloadToken();
  25:     }
  26:     //其他成員
  27: }

正是因為GetReloadToken方法并不能保證每次返回的都是同一個ConfigurationReloadToken對象,所以當我們注冊配置加載回調時,需要在回調中完成針對新的ConfigurationReloadToken對象的回調注冊,實際上我們上面演示的實例就是這么做的。除此之外,調用RegisterChangeCallback方法會返回一個類型實現了IDisposable 接口的對象,不要忘記調用它的Dispose方法以免產生一些內存泄漏的問題。

   1: public class Program
   2: {
   3:     private static IDisposable callbackRegistration;
   4:     private static void OnSettingChanged(object state)
   5:     {
   6:         callbackRegistration?.Dispose();
   7:         IConfiguration configuration = (IConfiguration)state;
   8:         Console.WriteLine(configuration.Get<ThreadPoolSettings>());
   9:         callbackRegistration = configuration.GetReloadToken()
  10:             .RegisterChangeCallback(OnSettingChanged, state);
  11:     }
  12: }

 

ASP.NET Core的配置(1):讀取配置信息
ASP.NET Core的配置(2):配置模型詳解
ASP.NET Core的配置(3): 將配置綁定為對象[上篇]
ASP.NET Core的配置(3): 將配置綁定為對象[下篇]
ASP.NET Core的配置(4):多樣性的配置源[上篇]
ASP.NET Core的配置(4):多樣性的配置源[中篇]
ASP.NET Core的配置(4):多樣性的配置源[下篇]
ASP.NET Core的配置(5):配置的同步[上篇]
ASP.NET Core的配置(5):配置的同步[下篇]


文章列表


不含病毒。www.avast.com
arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()