.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的技術小白