1. 程式人生 > 其它 >.net core中的Options重新載入機制ConfigurationChangeTokenSource

.net core中的Options重新載入機制ConfigurationChangeTokenSource

  Options是.net core提出的一種輔助配置機制,即選項。

  目前,我們可以使用的Options有五種(原始碼):

  IOptionsFactory<>:Options的建立工廠(Singleton),所有的Options均使用它僅限建立,可以使用名稱建立指定名稱的Options

  IOptions<>:常用的一種Options模式,不可指定名稱,且會快取Options,全域性將使用這個快取的Options,沒有重新載入機制,不可修改。(ps:可使用反射進行修改)

  IOptionsSnapshot<>:和IOptionsFactory<>差不多,內部使用IOptionsFactory<>來建立,但是IOptionsSnapshot<>內部有快取,且它的作用於是Scoped,做web開發的時候,如果不想自己實現重新載入,不妨使用IOptionsSnapshot<>。

  IOptionsMonitor<>和IOptionsMonitorCache<>:IOptionsMonitorCache<>是IOptionsMonitor<>的快取,讀取IOptionsMonitor<>時,會先從IOptionsMonitorCache<>快取中讀取,沒有則使用IOptionsFactory<>建立並加入到IOptionsMonitorCache<>快取中去,因此當配置發生改變時,我們需要重新載入才能得到新的Options,所謂重新載入,就是Options中的資料重新執行Configure以及PostConfigure中的方法。

  在繼續下面的內容之前,我們應該明確,只有IOptionsMonitor<>會涉及到重新載入,那麼何時需要重新載入?也就是何時觸發這個重新載入?不同的系統做法會不一樣,有的使用一條訊息匯流排,有的使用訊息的釋出訂閱,有的使用介面的手動觸發,本文介紹採用定時器來模擬說明:

  一、使用IOptionsMonitorCache<>

  上面介紹到,IOptionsMonitorCache<>是IOptionsMonitor<>快取,自然可以通過IOptionsMonitorCache<>來清除快取來實現Options的重新建立了,如:

  假如資料來源是一個類的資料,然後使用定時器定時重新整理,重新整理後需要清除IOptionsMonitorCache<>快取:  

    //表示Options的資料來源
    public class OptionsSource
    {
        public static DateTime Time { get; private set; }

        public OptionsSource(IServiceProvider serviceProvider)
        {
            Timer timer = new Timer();
            timer.Interval = 10000;//10秒鐘重新整理一次Time
            timer.Elapsed += new ElapsedEventHandler((o, e) =>
            {
                Time = DateTime.Now;

                //清除快取
                var options = serviceProvider.GetService(typeof(IOptionsMonitorCache<DemoOptions>)) as IOptionsMonitorCache<DemoOptions>;
                options.Clear();//或者使用TryRemove刪除某個名稱的Options
            });
            timer.Start();//啟動
        }
    }

  接著在Startup中註冊Options:  

    public void ConfigureServices(IServiceCollection services)
    {
        //註冊Options
        services.Configure<DemoOptions>(options =>
        {
            options.Time = OptionsSource.Time;
        });
        services.AddSingleton<OptionsSource>();//資料來源

        //其他服務注入程式碼
    }

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        app.ApplicationServices.GetRequiredService<OptionsSource>();//獲取一次,啟動定時器

        //其他程式碼
    }

  接下來可以使用IOptionsMonitor<>獲取Options,會發現每隔10秒,資料會重新整理一次,比如有一個介面讀取Options,每10秒呼叫試試:  

    [HttpGet]
    public object GetOptions()
    {
        var options = HttpContext.RequestServices.GetService(typeof(IOptionsMonitor<DemoOptions>)) as IOptionsMonitor<DemoOptions>;
        return options.CurrentValue;
    }

  二、使用IOptionsChangeTokenSource<>

  IOptionsChangeTokenSource<>是IOptionsMonitor<>用於觸發重新載入Options的介面,它有一個Name屬性,表示重新載入某個指定名稱的Options,還有一個GetChangeToken方法,用於獲取一個IChangeToken,由這個IChangeToken來觸發何時應該重新載入Options,比如:

  我們先實現IOptionsChangeTokenSource<>介面:

    public class MyOptionsChangeTokenSource<TOptions> : IOptionsChangeTokenSource<TOptions> where TOptions : class
    {
        ConfigurationReloadToken _reloadToken = new ConfigurationReloadToken();

        public MyOptionsChangeTokenSource(string name)
        {
            Name = name ?? Options.DefaultName;
        }

        public string Name { get; }

        public IChangeToken GetChangeToken()
        {
            return _reloadToken;
        }
        public void OnReload()
        {
            var previousToken = System.Threading.Interlocked.Exchange(ref _reloadToken, new ConfigurationReloadToken());
            previousToken.OnReload();
        }
    }

  其中OnReload()方法即觸發重新載入,何時觸發呢?假如配置的資料來源來自某個類的資料:  

    //表示Options的資料來源
    public class OptionsSource
    {
        public static DateTime Time { get; private set; }

        public OptionsSource(IOptionsChangeTokenSource<DemoOptions> optionsChangeTokenSource)
        {
            Timer timer = new Timer();
            timer.Interval = 10000;//10秒鐘重新整理一次Time
            timer.Elapsed += new ElapsedEventHandler((o, e) =>
            {
                Time = DateTime.Now;

                if (optionsChangeTokenSource is MyOptionsChangeTokenSource<DemoOptions> source)
                {
                    source.OnReload();//觸發MyOptionsChangeTokenSource<>從而重新載入Options
                }
            });
            timer.Start();//啟動
        }
    }

  接著在Startup中註冊Options:  

    public void ConfigureServices(IServiceCollection services)
    {
        //註冊Options
        services.Configure<DemoOptions>(options =>
        {
            options.Time = OptionsSource.Time;
        });
        services.AddSingleton<OptionsSource>();//資料來源
        services.AddSingleton<IOptionsChangeTokenSource<DemoOptions>>(new MyOptionsChangeTokenSource<DemoOptions>(""));

        //其他程式碼
    }

    // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        app.ApplicationServices.GetRequiredService<OptionsSource>();//獲取一次,啟動定時器

        //其他程式碼
    }

  接下來可以使用IOptionsMonitor<>獲取Options也會有重新載入的效果了。

  上面的例子中,資料來源來自某個類,但是開發過程中,我們的Options中的資料往往來自IConfiguration配置中,這時就更簡單了,框架已經給我們準備好了一個ConfigurationChangeTokenSource<>類,當我們使用IConfiguration來配置Options時會自動注入這個類,我們當然也可以手動注入,意思就是說,當對應的IConfiguration中的資料發生改變時,它會觸發清除對應的IOptionsMonitorCache<>快取從而導致重新載入,比如:

  appsettings.json中有下面的json片段:  

  {
    "Demo": {
      "Value": 1
    }
  }

  程式啟動時會自動載入appsettings.json檔案中的配置到IConfiguration中,所以我們可以直接從IConfiguration配置Options:  

    public void ConfigureServices(IServiceCollection services)
    {
        //方式一:
        //這個會新增ConfigurationChangeTokenSource<>類,程式啟動後,修改appsettings.json會觸發Options的重新載入
        services.Configure<DemoOptions>(Configuration.GetSection("Demo"));

        //方式二:
        //這種方式不會新增ConfigurationChangeTokenSource<>類,但是可以手動新增
        services.Configure<DemoOptions>(options =>
        {
            Configuration.GetSection("Demo").Bind(options);
        });
        services.AddSingleton<IOptionsChangeTokenSource<DemoOptions>>(new ConfigurationChangeTokenSource<DemoOptions>(Options.DefaultName, Configuration.GetSection("Demo")));

        //其他程式碼
    }

  如上所述,新增Options的Configure方法有很多,但是隻用 OptionsConfigurationServiceCollectionExtensions 類中的Configure方法才會自動新增ConfigurationChangeTokenSource<>類,即Configure方法引數中有IConfiguration引數。

  結語

  Options的重新載入,如果看看原始碼就會很清晰,但是更多的,希望在讀取Options的時候多注意一下吧:

  1、如果配置永遠不會發生改變,或者改變或需要重啟才能生效(這種配置往往是在啟動階段用到),那麼儘量使用IOptions<>

  2、如果要實現配置修改後會自動載入Options,推薦使用OptionsMonitor<>,但是注意,要自行實現何時需要重新載入,其次可以使用IOptionsSnapshot<>,因為它是一個Scoped作用域,最次是IOptionsFactory<>,每次都是重新建立可能會導致一些效能的損失

一個專注於.NetCore的技術小白