外部配置存储模式
移动配置信息从应用部署包到一个集中位置。这个模式可以提供机会,以便管理和配置数据的控制,以及用于跨应用程序和应用程序实例共享的配置数据。
背景和问题
大多数应用程序运行时环境包括位于应用程序文件夹内的在部署应用程序文件保持配置信息。在某些情况下也能够编辑这些文件来改变该应用程序的行为,它已经被部署之后。然而,在许多情况下,改变配置所需要的应用程序被重新部署,从而导致不可接受的停机时间和额外的管理开销。
本地配置文件还配置限制为单个应用程序,而在某些情况下将是有用的,以在多个应用程序之间共享的配置设置。例子包括数据库连接字符串,UI 主题的信息,或队列和存储所使用的一组相关的应用程序的URL。
变更管理跨应用程序的多个运行实例的本地配置,尤其是在云托管的情况,也可能是具有挑战性的。它可能会导致使用不同的配置设置的实例,而更新正被部署。
另外,更新应用程序和组件可能需要更改的配置方案。许多配置系统不支持不同版本的配置信息。
解决方案
存储在外部存储器中的配置信息,并提供可用于快速和有效地读取和更新的配置设置的接口。外部存储的类型取决于应用程序的主机和运行时环境。在一个云托管的情况下它是一个典型的基于云的存储服务,但可能是一个托管数据库或其他系统。
选择用于配置信息的备份存储应通过适当的接口,它提供了一个可控制的方式,使回用保持一致和易于使用的访问被朝向。理想情况下,它应该公开在键入正确,结构化的格式的信息。的实施也可能需要对用户进行授权“,以保护结构的数据访问,并且具有足够的灵活性,以允许要被存储的多个版本的配置(例如,开发,分段,或生产,并且每一个的多个发行版本)。
注意: 许多内置的系统配置中读取数据时,应用程序启动和高速缓存内存中的数据提供快速访问,并尽量减少对应用程序性能的影响。根据所使用的后备存储器的类型,以及该商店的等待时间,这可能是有利的,以实现外部配置存储器内的高速缓存机制。有关实现缓存的详细信息,请参阅缓存指导。
图1示出了本模式的概述。
图1 - 外部配置存储模式可选本地缓存概述
问题和注意事项
在决定如何实现这个模式时,请考虑以下几点:
- 选择一个后备存储,提供可接受的性能,高可用性,健壮性和可备份作为应用程序的维护和管理过程的一部分。在一个云托管的应用程序,使用云存储的机制通常是一个不错的选择,以满足这些要求。
- 设计的后备存储的架构允许在信息能够保存类型的灵活性。确保它提供了一种使用它可以要求该申请的所有配置的要求,例如输入数据中,设置的集合,多个版本的设置,以及任何其他功能。该模式应该是易于扩展的需求,以支持更多的设置更改。
- 考虑后备存储的物理性能,它与配置信息的存储方式,以及对性能的影响。例如,存储一个包含XML文件的配置信息将要求使用配置界面或应用程序解析该文件以读取各个设置,将使得更新的设置更加复杂,尽管高速缓存中的设置可有助于抵消较慢的读取性能。
- 考虑如何配置界面将允许配置设置的范围和继承的控制权。例如,它可能是一个要求的范围的配置设置在组织,应用程序和设备的水平;支持在访问不同范围的控制下放;并且,以防止或允许单独的应用程序,以覆盖设置。
- 确保配置界面可以在需要的格式的配置数据暴露,如输入值的集合,键/值对,或财产包。然而,考虑能力和API的复杂性之间的平衡,以使其有用的,但尽可能地易于使用。
- 考虑配置存储界面将如何表现时,设定有误,或没有在内部存储存在。它可能是适当的,返回默认设置和记录错误。也可以考虑,如配置设置按键或者名称,二进制数据的存储和处理,以及null或空值处理方式的情况下,灵敏度方面。
- 考虑如何将保护配置数据仅允许访问相应的用户和应用程序。这很可能是在配置存储器接口的一个特征,但它也是必要的,以确保在后备存储器中的数据不能被直接访问,而不适当的权限。确保读取和写入配置数据所需的权限之间的严格分离。也可以考虑是否需要加密部分的配置设置或全部,以及如何将配置存储接口中实现。
- 请记住,集中存储配置,这在运行时改变应用程序的行为,是非常重要的,并应部署,更新,并使用相同的机制,部署应用程序代码进行管理。例如,可能会影响多个应用程序的更改,必须进行使用一个完整的测试和分阶段部署的方式,以确保变化是适用于所有使用该配置的应用程序。如果管理员简单地进行编辑的设置来更新一个应用程序,它可以产生不利使用相同设置的其他应用程序的影响。
- 如果应用程序的高速缓存的配置信息,应用程序可能需要的,如果配置更改被提醒。有可能实现的过期策略在缓存中的配置数据,使这些信息被自动定期刷新任何更改拿起(和付诸行动)。本指南中其他地方所描述的运行模式重构可能有关您的方案。 何时使用这个模式
这种模式非常适合于:
- 被多个应用程序和应用程序实例,或在标准配置中,必须跨多个应用程序和应用程序实例执行之间共享配置设置。
- 在标准配置的系统不支持所有所需的配置设置,如存储图像或复杂的数据类型。
- 作为补充商店的一些应用程序的设置,或许允许应用程序重写一些集中存储或所有设置。
- 作为一种机制,通过记录的部分或全部类型的访问来配置存储监控使用的配置设置简化了多个应用程序管理,以及可选。
例子
在微软 Azure 托管应用,用于从外部存储配置信息的典型的选择是使用 Azure 存储。这是有弹性的,提供高性能,并重复 3 次自动故障切换提供高可用性。 Azure 的表格提供了一个键/值存储与使用一个灵活的架构的价值的能力。 Azure 的 Blob 存储提供了一个分层的基于容器的存储,可以保存任何类型的单独命名的 blob 数据。
下面的示例显示了如何配置存储可以通过 Azure 的 Blob 存储来实现存储和揭露的配置信息。该BlobSettingsStore 类文摘 Blob 存储用于保存配置信息,并实现在下面的代码所示 ISettingsStore 接口。
注意: 此代码在 ExternalConfigurationStore 解决方案 ExternalConfigurationStore.Cloud 项目提供。该解决方案可用于下载本指导意见。
public interface IsettingsStore
{
string Version { get; }
Dictionary<string, string> FindAll();
void Update(string key, string value);
}
该接口定义的方法,用于检索和更新在配置存储中保持的配置设置,并且包括可用于检测是否有任何配置设置最近已修改的版本号。何时配置设置被更新时,版本号的变化。该 BlobSettingsStore 类使用 BLOB 的 ETag 的属性来实现的版本。一个 blob 的 ETag 的属性将 BLOB 写入每一次自动更新。
注意
需要注意的是,按照设计,这个简单的解决方案,展现了所有的配置设置为字符串值,而不是类型的值。 该 ExternalConfigurationManager 类提供了围绕 BlobSettingsStore 物体的包装。应用程序可以使用这个类来存储和检索配置信息。这个类使用 Microsoft 无扩展库来揭露过的 IObservable 接口的实现做出任何配置更改。如果设置是通过调用SetAppSetting法修改,更改的事件引发,所有订阅者此事件将被通报。 请注意,所有的设置也缓存到 ExternalConfigurationManager 类快速访问内部 Dictionary 对象。该 SetAppSetting 方法更新该高速缓存中,并且该应用程序可以使用以检索配置设置的 GetSetting 方法从高速缓存中读取数据(如果未在该高速缓存中找到该设置,它从 BlobSettingsStore 对象检索代替)。
所述的 getSettings 方法调用 CheckForConfigurationChanges 的方法来检测在 Blob 存储的配置信息是否通过检查版本号,并将它与所述 ExternalConfigurationManager 对象保持当前的版本号进行比较已经改变。如果一个或多个已经发生了变化,改变的事件引发,并缓存在 Dictionary 对象的配置设置被刷新。这是缓存除了图案的应用。
下面的代码示例演示如何更改的情况下,SetAppSettings 方法,该方法的 getSettings 和 CheckForConfigurationChanges 方法实现
public class ExternalConfigurationManager : IDisposable
{
// An abstraction of the configuration store.
private readonly ISettingsStore settings;
private readonly ISubject<KeyValuePair<string, string>> changed;
...
private Dictionary<string, string> settingsCache;
private string currentVersion;
...
public ExternalConfigurationManager(ISettingsStore settings, ...)
{
this.settings = settings;
...
}
...
public IObservable<KeyValuePair<string, string>> Changed
{
get { return this.changed.AsObservable(); }
}
...
public void SetAppSetting(string key, string value)
{
...
// Update the setting in the store.
this.settings.Update(key, value);
// Publish the event.
this.Changed.OnNext(
new KeyValuePair<string, string>(key, value));
// Refresh the settings cache.
this.CheckForConfigurationChanges();
}
public string GetAppSetting(string key)
{
...
// Try to get the value from the settings cache.
// If there is a miss, get the setting from the settings store.
string value;
if (this.settingsCache.TryGetValue(key, out value))
{
return value;
}
// Check for changes and refresh the cache.
this.CheckForConfigurationChanges();
return this.settingsCache[key];
}
...
private void CheckForConfigurationChanges()
{
try
{
// Assume that updates are infrequent. Lock to avoid
// race conditions when refreshing the cache.
lock (this.settingsSyncObject)
{ {
var latestVersion = this.settings.Version;
// If the versions differ, the configuration has changed.
if (this.currentVersion != latestVersion)
{
// Get the latest settings from the settings store and publish the changes.
var latestSettings = this.settings.FindAll();
latestSettings.Except(this.settingsCache).ToList().ForEach(
kv => this.changed.OnNext(kv));
// Update the current version.
this.currentVersion = latestVersion;
// Refresh settings cache.
this.settingsCache = latestSettings;
}
}
}
catch (Exception ex)
{
this.changed.OnError(ex);
}
}
}
注意
该 ExternalConfigurationManager 类还提供了一个名为 Environment 属性。此属性的目的是为了支持不同的配置为在不同的环境中,如临时和生产运行的应用程序。
一个 ExternalConfigurationManager 对象也可以定期查询 BlobSettingsStore 对象的任何变化(通过使用定时器)。该 StartMonitor 和 StopMonitor 方法如下图所示的启动代码示例和停止计时器。该 OnTimerElapsed 方法当定时器到期时,并调用 CheckForConfigurationChanges 方法来检测的任何变化,并提高了变更的情况下,如前面所描述运行。
public class ExternalConfigurationManager : IDisposable
{
...
private readonly ISubject<KeyValuePair<string, string>> changed;
private readonly Timer timer;
private ISettingsStore settings;
...
public ExternalConfigurationManager(ISettingsStore settings,
TimeSpan interval, ...)
{
...
// Set up the timer.
this.timer = new Timer(interval.TotalMilliseconds)
{
AutoReset = false;
};
this.timer.Elapsed += this.OnTimerElapsed;
this.changed = new Subject<KeyValuePair<string, string>>();
...
}
...
public void StartMonitor()
{
if (this.timer.Enabled)
{
return;
}
lock (this.timerSyncObject)
{
if (this.timer.Enabled)
{
return;
}
this.keepMonitoring = true;
// Load the local settings cache.
this.CheckForConfigurationChanges();
this.timer.Start();
}
}
public void StopMonitor()
{
lock (this.timerSyncObject)
{
this.keepMonitoring = false;
this.timer.Stop();
}
}
private void OnTimerElapsed(object sender, EventArgs e)
{
Trace.TraceInformation(
"Configuration Manager: checking for configuration changes.");
try
{
this.CheckForConfigurationChanges();
}
finally
{
...
// Restart the timer after each interval.
this.timer.Start();
...
}
}
...
}
该 ExternalConfigurationManager 类被实例化作为由 ExternalConfiguration 类如下所示的单一实例。
public static class ExternalConfiguration
{
private static readonly Lazy<ExternalConfigurationManager> configuredInstance
= new Lazy<ExternalConfigurationManager>(
() =>
{
var environment = CloudConfigurationManager.GetSetting("environment");
return new ExternalConfigurationManager(environment);
});
public static ExternalConfigurationManager Instance
{
get { return configuredInstance.Value; }
}
}
下面的代码取自 WorkerRole 类中 ExternalConfigurationStore.Cloud 项目。它显示了如何在应用程序使用 ExternalConfiguration 类读取和更新设置。
public override void Run()
{
// Start monitoring for configuration changes.
ExternalConfiguration.Instance.StartMonitor();
// Get a setting.
var setting = ExternalConfiguration.Instance.GetAppSetting("setting1");
Trace.TraceInformation("Worker Role: Get setting1, value: " + setting);
Thread.Sleep(TimeSpan.FromSeconds(10));
// Update a setting.
Trace.TraceInformation("Worker Role: Updating configuration");
ExternalConfiguration.Instance.SetAppSetting("setting1", "new value");
this.completeEvent.WaitOne();
}
下面的代码,也是从 WorkerRole 类,展示了如何应用订阅配置事件。
public override bool OnStart()
{
...
// Subscribe to the event.
ExternalConfiguration.Instance.Changed.Subscribe(
m => Trace.TraceInformation("Configuration has changed. Key:{0} Value:{1}",
m.Key, m.Value),
ex => Trace.TraceError("Error detected: " + ex.Message));
...
}