.NET Core采用的全新配置系统[10]: 配置的同步机制是如何实现的?

配置的同步涉及到两个方面:第一,对原始的配置文件实施监控并在其发生变化之后从新加载配置;第二,配置重新加载之后及时通知应用程序进而使后者能够使用最新的配置。要了解配置同步机制的实现原理,先得从认识一个名为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]: 配置的同步机制是如何实现的?

作者:蒋金楠
微信公众账号:大内老A
微博:www.weibo.com/artech
如果你想及时得到个人撰写文章以及著作的消息推送,或者想看看个人推荐的技术资料,可以扫描左边二维码(或者长按识别二维码)关注个人公众号(原来公众帐号蒋金楠的自媒体将会停用)。
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

原文链接

时间: 2025-01-31 06:30:05

.NET Core采用的全新配置系统[10]: 配置的同步机制是如何实现的?的相关文章

.NET Core采用的全新配置系统[1]: 读取配置数据

提到"配置"二字,我想绝大部分.NET开发人员脑海中会立马浮现出两个特殊文件的身影,那就是我们再熟悉不过的app.config和web.config,多年以来我们已经习惯了将结构化的配置定义在这两个文件之中.到了.NET Core的时代,很多我们习以为常的东西都发生了改变,其中也包括定义配置的方式.总的来说,新的配置系统显得更加轻量级,并且具有更好的扩展性,其最大的特点就是支持多样化的数据源.我们可以采用内存的变量作为配置的数据源,也可以直接配置定义在持久化的文件甚至数据库中.由于很多

.NET Core采用的全新配置系统[2]: 配置模型设计详解

在<.NET Core采用的全新配置系统[1]: 读取配置数据>中,我们通过实例的方式演示了几种典型的配置读取方式,其主要目的在于使读者朋友们从编程的角度对.NET Core的这个全新的配置系统具有一个大体上的认识,接下来我们从设计的维度来重写认识它.通过上面演示的实例我们知道,配置的编程模型涉及到三个核心对象,它们分别是Configuration.ConfigurationSource和ConfigurationBuilder.如果从设计层面来审视这个配置系统,还缺少另一个名为Configu

.NET Core采用的全新配置系统[3]: “Options模式”下的配置是如何绑定为Options对象

配置的原子结构就是单纯的键值对,并且键和值都是字符串,但是在真正的项目开发中我们一般不会单纯地以键值对的形式来使用配置.值得推荐的做法就是采用<.NET Core采用的全新配置系统[1]: 读取配置数据>最后演示的方式将相关的配置定义成一个Options类型,并采用与类型定义想匹配的结构来定义原始的配置,这样就能利用它们之间的映射关系将读取的配置数据绑定为Options对象,我们将这种编程模式称为"Options模式". [ 本文已经同步到<ASP.NET Core框

.NET Core采用的全新配置系统[5]: 聊聊默认支持的各种配置源[内存变量,环境变量和命令行参数]

较之传统通过App.config和Web.config这两个XML文件承载的配置系统,.NET Core采用的这个全新的配置模型的最大一个优势就是针对多种不同配置源的支持.我们可以将内存变量.命令行参数.环境变量和物理文件作为原始配置数据的来源,如果采用物理文件作为配置源,我们可以选择不同的格式(比如XML.JSON和INI等) .如果这些默认支持的配置源形式还不能满足你的需求,我们还可以通过注册自定义ConfigurationSource的方式将其他形式数据作为我们的配置来源. [ 本文已经同

.NET Core采用的全新配置系统[7]: 将配置保存在数据库中

我们在<聊聊默认支持的各种配置源>和<深入了解三种针对文件(JSON.XML与INI)的配置源>对配置模型中默认提供的各种ConfigurationSource进行了深入详尽的介绍,如果它们依然不能满足项目中的配置需求,我们可以还可以通过自定义ConfigurationProvider来支持我们希望的配置来源.就配置数据的持久化方式来说,将培植存储在数据库中应该是一种非常常见的方式,接下来我们就是创建一个针对数据库的ConfigurationSource,它采用最新的Entity

.NET Core采用的全新配置系统[8]: 如何实现配置与源文件的同步

配置的同步涉及到两个方面:第一,对原始的配置文件实施监控并在其发生变化之后从新加载配置:第二,配置重新加载之后及时通知应用程序进而使后者能够使用最新的配置.接下来我们利用一个简单的.NET Core控制台应用来演示针对文件的配置会涉及到数据同步的问题,我们希望应用能够对原始配置文件实施监控,并在文件内容发生改变的时候从新加载并应用新的配置.针对JSON文件的配置源通过JsonConfigurationSource类型来表示,该类型定义在"Microsoft.Extensions.Configur

.NET Core采用的全新配置系统[6]: 深入了解三种针对文件(JSON、XML与INI)的配置源

物理文件是我们最常用到的原始配置的载体,最佳的配置文件格式主要由三种,它们分别是JSON.XML和INI,对应的配置源类型分别是JsonConfigurationSource.XmlConfigurationSource和IniConfigurationSource. [ 本文已经同步到<ASP.NET Core框架揭秘>之中] 目录 一.FileConfigurationSource  & FileConfigurationProvider 二.JsonConfigurationSo

.NET Core采用的全新配置系统[4]: “Options模式”下各种类型的Options对象是如何绑定的?

旨在生成Options对象的配置绑定实现在IConfiguration接口的扩展方法Bind上.配置绑定的目标类型可以是一个简单的基元类型,也可以是一个自定义数据类型,还可以是一个数组.集合或者字典类型.通过前面的介绍我们知道ConfigurationProvider将原始的配置数据读取出来后会将其转成Key和Value均为字符串的数据字典,那么针对这些完全不同的目标类型,原始的配置数据如何通过数据字典的形式来体现呢? [ 本文已经同步到<ASP.NET Core框架揭秘>之中] 目录 一.绑

PHP-5.5.10+Apache httpd-2.4.9在Windows系统下配置实战

原文 PHP-5.5.10+Apache httpd-2.4.9在Windows系统下配置实战 环境配置:   程序准备: PHP windows版本下载地址: http://windows.php.net/downloads/releases/php-5.5.10-Win32-VC11-x64.zip (下载后文件名为php-5.5.10-Win32-VC11-x64.zip)  Apache httpd windows版本下载地址: http://www.apachelounge.com/d