Azure设计模式之外配存储模式
外部配置存储模式
将配置信息从应用程序中抽出来集中管理。可以更方便地管理和控制配置数据并实现跨应用程序实现共享配置。
问题背景
大多数应用程序的运行环境包括应用程序部署文件中的配置信息。在某些情况下,可以通过编辑这些文件来更改应用程序运行时的行为。但是,对配置的更改要求重新部署应用程序,这会导致不可避免的停机时间和其他管理开销。
本地配置文件也会限制配置为单个应用程序,但有时在多个应用程序之间需要共享配置。例如数据库连接字符串、UI主题信息或相关应用程序集所使用的队列或存储相关的url。
在应用程序的多个运行实例中管理本地配置的更改非常困难,尤其是在云环境下。在部署更新时,它可能会导致使用运行的实例使用了不同的配置。此外,对应用程序和组件的更新可能需要更改配置架构。许多配置系统不能支持不同版本的配置信息。
解决方案
将配置信息存储在外部存储中,并提供一个可用于快速、高效读取和更新配置的接口。外部存储类型取决于应用程序的宿主和运行环境。在云托管方案中,它通常是云存储服务,但也可以是托管数据库或其他系统。
为配置信息选择的外部存储应该提供一致且易于使用的接口。它应该以正确类型和结构化的格式暴露信息。可能还需要对用户访问进行授权,从而保护配置数据,并且需要足够灵活,允许存储多版本的配置(如开发,staging,生产环境,每个环境支持多版本发布)。
许多内置的配置系统在应用程序启动时读数据,并将数据缓存到内存中以提供快速访问并最大限度地减少对应用程序性能的影响。根据所使用的后备存储类型和存储的滞后时间,在外部配置存储区中实现缓存机制可能会有所帮助。有关详细信息,请参阅缓存指南(https://msdn.microsoft.com/library/dn589802.aspx)。下图显示了含有本地缓存(可选)的外部配置存储模式的结构。
问题和注意事项
在决定如何实现此模式时, 请考虑以下几点:
选择一个性能上可以接受的、高可用性和健壮性好的后备存储,并可作为应用程序维护和管理过程的一部分,并且要进行备份。在云环境的应用程序中,使用云存储机制通常是满足这些要求的不错选择。设计后备存储区的架构,并且信息类型具有灵活性。确保它提供了所有配置要求,如类型化数据、设置集合、多个版本的设置,以及使用它的应用程序所需的任何其他功能。随着需求的变化,架构应该很容易扩展以支持其他设置。
考虑后备存储的物理功能、它与配置信息的存储方式以及对性能的影响。例如,存储包含配置信息的XML文档将需要配置接口或应用程序来分析文档,才能读取单个设置。它会使更新设置变得更加复杂, 尽管缓存这些设置可以降低对读性能的影响。
考虑配置接口如何对使用范围和继承进行控制。例如,可能需要对组织、应用程序和计算机级别的配置设置进行范围限定。可能需要支持对不同作用域的访问进行控制,防止或允许单个应用程序重写设置。
确保配置接口可以以所需的格式(如类型化值、集合、键/值对或属性包)暴露配置数据。
考虑配置存储接口在设置包含错误时或者不存在于后备存储区中的行为。返回默认设置和日志错误可能是适当的。还要考虑一些方面,例如配置设置键或名称的区分大小写、二进制数据的存储和处理,以及null或空值的处理方式。
考虑如何保护配置数据,以便只允许经过验证的用户和应用程序访问。这可能是配置存储接口的一个功能,但也有必要确保在没有适当权限的情况下无法直接访问数据。确保已经建立了严格的配置数据的读写权限。还要考虑是否需要对部分或全部配置设置进行加密,以及如何在配置存储界面中实现此功能。
集中存储的配置在运行时更改应用程序行为至关重要,应该使用与部署应用程序代码相同的机制来部署、更新和管理。例如,可能影响多个应用程序的更改必须使用完整的测试和阶段化部署的方法来执行,以确保更改适用于使用此配置的所有应用程序。如果管理员编辑某个设置只更新一个应用程序,则它可能会对使用相同设置的其他应用程序产生负面影响。
如果应用程序缓存配置信息,则需要在配置更改时通知应用程序。可以通过缓存配置数据实现过期策略,以便定期自动刷新此信息, 并应用到所做的任何更改(并采取操作)中。
何时使用此模式
此模式适用于:
多个应用程序和应用程序实例之间需要共享的配置,或必须在多个应用程序和应用程序实例中强制执行标准配置。
不支持配置机制(如存储图像或复杂数据类型)的系统。
作为应用程序某些设置的互补存储,可能会允许应用程序重写部分或全部集中存储的设置。
作为一种简化多个应用程序管理的方法,还可以通过记录对配置存储的某些或所有类型的访问来监视配置设置的使用。
例子
在Microsoft azure托管应用程序中,从外部存储配置信息的一个典型选择是使用azure存储。这种机制是有弹性的,并且提高性能,通过三重备份和自动故障转移以提供高可用性。AzureTable提供了键/值存储,它为这些值的使用提供灵活的架构。AzureBlob存储提供了一个分层基于容器的存储形式,可以在每个blob中保存任何类型的数据。
下面的示例演示了如何通过Blob存储实现配置的存储,以存储和公开配置信息。BlobSettingsStore类为Blob存储保存配置信息,并实现ISettingsStore接口。
以下在ExternalConfigurationStore解决方案的 ExternalConfigurationStore 中提供, 可从 GitHub下载(https://github.com/mspnp/cloud-design-patterns/tree/master/external-configuration-store)。
该接口中定义了用于检索和更新配置的方法,并包括了版本号,用于检测最近是否修改过任何配置。BlobSettingsStore类使用 blob的ETag属性来实现版本控制。每次写入blob时,ETag属性都会自动更新。
当前解决方案将所有配置暴露为字符串,而不是类型化值。ExternalConfigurationManager类对BlobSettingsStore进行了封装。应用程序可以使用此类来存储和检索配置信息。此类使用Microsoft Reactive Extensions(https://msdn.microsoft.com/library/hh242985.aspx)库中IObservable 接口监听对配置所做的任何更改。如果通过调用SetAppSetting方法修改了某个设置,将触发更改事件,并将通知此事件的所有订阅者。
注意,所有设置也都缓存在ExternalConfigurationManager类中的字典对象中,用于快速访问。用于检索配置设置的GetSetting 方法从缓存中读取数据。如果在缓存中找不到设置,再从BlobSettingsStore对象中检索。
GetSettings方法调用CheckForConfigurationChanges方法来检测blob存储中的配置信息是否已更改。通过检查版本号并将其与ExternalConfigurationManager对象持有的当前版本号进行比较来做到这一点。如果发生了一个或多个更改,则会触发更改的事件,并刷新在字典对象中缓存的配置。这是应用程序的缓存模式(https://docs.microsoft.com/en-us/azure/architecture/patterns/cache-aside)。
下面的代码示例演示了如何实现更改事件、GetSettings方法和CheckForConfigurationChanges方法:
ExternalConfigurationManager对象还可以定期查询BlobSettingsStore对象以执行更改请求。在下面的代码中,StartMonitor方法按指定时间间隔调用 CheckForConfigurationChanges 以检测任何更改事件, 如前面所述。
相关的模式和指导
GitHub(https://github.com/mspnp/cloud-design-patterns/tree/master/external-configuration-store)上提供了演示此模式的示例。
将配置信息从应用程序中抽出来集中管理。可以更方便地管理和控制配置数据并实现跨应用程序实现共享配置。
问题背景
大多数应用程序的运行环境包括应用程序部署文件中的配置信息。在某些情况下,可以通过编辑这些文件来更改应用程序运行时的行为。但是,对配置的更改要求重新部署应用程序,这会导致不可避免的停机时间和其他管理开销。
本地配置文件也会限制配置为单个应用程序,但有时在多个应用程序之间需要共享配置。例如数据库连接字符串、UI主题信息或相关应用程序集所使用的队列或存储相关的url。
在应用程序的多个运行实例中管理本地配置的更改非常困难,尤其是在云环境下。在部署更新时,它可能会导致使用运行的实例使用了不同的配置。此外,对应用程序和组件的更新可能需要更改配置架构。许多配置系统不能支持不同版本的配置信息。
解决方案
将配置信息存储在外部存储中,并提供一个可用于快速、高效读取和更新配置的接口。外部存储类型取决于应用程序的宿主和运行环境。在云托管方案中,它通常是云存储服务,但也可以是托管数据库或其他系统。
为配置信息选择的外部存储应该提供一致且易于使用的接口。它应该以正确类型和结构化的格式暴露信息。可能还需要对用户访问进行授权,从而保护配置数据,并且需要足够灵活,允许存储多版本的配置(如开发,staging,生产环境,每个环境支持多版本发布)。
许多内置的配置系统在应用程序启动时读数据,并将数据缓存到内存中以提供快速访问并最大限度地减少对应用程序性能的影响。根据所使用的后备存储类型和存储的滞后时间,在外部配置存储区中实现缓存机制可能会有所帮助。有关详细信息,请参阅缓存指南(https://msdn.microsoft.com/library/dn589802.aspx)。下图显示了含有本地缓存(可选)的外部配置存储模式的结构。
问题和注意事项
在决定如何实现此模式时, 请考虑以下几点:
选择一个性能上可以接受的、高可用性和健壮性好的后备存储,并可作为应用程序维护和管理过程的一部分,并且要进行备份。在云环境的应用程序中,使用云存储机制通常是满足这些要求的不错选择。设计后备存储区的架构,并且信息类型具有灵活性。确保它提供了所有配置要求,如类型化数据、设置集合、多个版本的设置,以及使用它的应用程序所需的任何其他功能。随着需求的变化,架构应该很容易扩展以支持其他设置。
考虑后备存储的物理功能、它与配置信息的存储方式以及对性能的影响。例如,存储包含配置信息的XML文档将需要配置接口或应用程序来分析文档,才能读取单个设置。它会使更新设置变得更加复杂, 尽管缓存这些设置可以降低对读性能的影响。
考虑配置接口如何对使用范围和继承进行控制。例如,可能需要对组织、应用程序和计算机级别的配置设置进行范围限定。可能需要支持对不同作用域的访问进行控制,防止或允许单个应用程序重写设置。
确保配置接口可以以所需的格式(如类型化值、集合、键/值对或属性包)暴露配置数据。
考虑配置存储接口在设置包含错误时或者不存在于后备存储区中的行为。返回默认设置和日志错误可能是适当的。还要考虑一些方面,例如配置设置键或名称的区分大小写、二进制数据的存储和处理,以及null或空值的处理方式。
考虑如何保护配置数据,以便只允许经过验证的用户和应用程序访问。这可能是配置存储接口的一个功能,但也有必要确保在没有适当权限的情况下无法直接访问数据。确保已经建立了严格的配置数据的读写权限。还要考虑是否需要对部分或全部配置设置进行加密,以及如何在配置存储界面中实现此功能。
集中存储的配置在运行时更改应用程序行为至关重要,应该使用与部署应用程序代码相同的机制来部署、更新和管理。例如,可能影响多个应用程序的更改必须使用完整的测试和阶段化部署的方法来执行,以确保更改适用于使用此配置的所有应用程序。如果管理员编辑某个设置只更新一个应用程序,则它可能会对使用相同设置的其他应用程序产生负面影响。
如果应用程序缓存配置信息,则需要在配置更改时通知应用程序。可以通过缓存配置数据实现过期策略,以便定期自动刷新此信息, 并应用到所做的任何更改(并采取操作)中。
何时使用此模式
此模式适用于:
多个应用程序和应用程序实例之间需要共享的配置,或必须在多个应用程序和应用程序实例中强制执行标准配置。
不支持配置机制(如存储图像或复杂数据类型)的系统。
作为应用程序某些设置的互补存储,可能会允许应用程序重写部分或全部集中存储的设置。
作为一种简化多个应用程序管理的方法,还可以通过记录对配置存储的某些或所有类型的访问来监视配置设置的使用。
例子
在Microsoft azure托管应用程序中,从外部存储配置信息的一个典型选择是使用azure存储。这种机制是有弹性的,并且提高性能,通过三重备份和自动故障转移以提供高可用性。AzureTable提供了键/值存储,它为这些值的使用提供灵活的架构。AzureBlob存储提供了一个分层基于容器的存储形式,可以在每个blob中保存任何类型的数据。
下面的示例演示了如何通过Blob存储实现配置的存储,以存储和公开配置信息。BlobSettingsStore类为Blob存储保存配置信息,并实现ISettingsStore接口。
以下在ExternalConfigurationStore解决方案的 ExternalConfigurationStore 中提供, 可从 GitHub下载(https://github.com/mspnp/cloud-design-patterns/tree/master/external-configuration-store)。
public interface ISettingsStore
{
Task<string> GetVersionAsync();
Task<Dictionary<string, string>> FindAllAsync();
}
该接口中定义了用于检索和更新配置的方法,并包括了版本号,用于检测最近是否修改过任何配置。BlobSettingsStore类使用 blob的ETag属性来实现版本控制。每次写入blob时,ETag属性都会自动更新。
当前解决方案将所有配置暴露为字符串,而不是类型化值。ExternalConfigurationManager类对BlobSettingsStore进行了封装。应用程序可以使用此类来存储和检索配置信息。此类使用Microsoft Reactive Extensions(https://msdn.microsoft.com/library/hh242985.aspx)库中IObservable 接口监听对配置所做的任何更改。如果通过调用SetAppSetting方法修改了某个设置,将触发更改事件,并将通知此事件的所有订阅者。
注意,所有设置也都缓存在ExternalConfigurationManager类中的字典对象中,用于快速访问。用于检索配置设置的GetSetting 方法从缓存中读取数据。如果在缓存中找不到设置,再从BlobSettingsStore对象中检索。
GetSettings方法调用CheckForConfigurationChanges方法来检测blob存储中的配置信息是否已更改。通过检查版本号并将其与ExternalConfigurationManager对象持有的当前版本号进行比较来做到这一点。如果发生了一个或多个更改,则会触发更改的事件,并刷新在字典对象中缓存的配置。这是应用程序的缓存模式(https://docs.microsoft.com/en-us/azure/architecture/patterns/cache-aside)。
下面的代码示例演示了如何实现更改事件、GetSettings方法和CheckForConfigurationChanges方法:
public class ExternalConfigurationManager : IDisposable
{
// An abstraction of the configuration store.
private readonly ISettingsStore settings;
private readonly ISubject<KeyValuePair<string, string>> changed;
...
private readonly ReaderWriterLockSlim settingsCacheLock = new ReaderWriterLockSlim();
private readonly SemaphoreSlim syncCacheSemaphore = new SemaphoreSlim(1);
...
private Dictionary<string, string> settingsCache;
private string currentVersion;
...
public ExternalConfigurationManager(ISettingsStore settings, ...)
{
this.settings = settings;
...
}
...
public IObservable<KeyValuePair<string, string>> Changed => this.changed.AsObservable();
...
public string GetAppSetting(string key)
{
...
// Try to get the value from the settings cache.
// If there's a cache miss, get the setting from the settings store and refresh the settings cache.
string value;
try
{
this.settingsCacheLock.EnterReadLock();
this.settingsCache.TryGetValue(key, out value);
}
finally
{
this.settingsCacheLock.ExitReadLock();
}
return value;
}
...
private void CheckForConfigurationChanges()
{
try
{
// It is assumed that updates are infrequent.
// To avoid race conditions in refreshing the cache, synchronize access to the in-memory cache.
await this.syncCacheSemaphore.WaitAsync();
var latestVersion = await this.settings.GetVersionAsync();
// If the versions are the same, nothing has changed in the configuration.
if (this.currentVersion == latestVersion) return;
// Get the latest settings from the settings store and publish changes.
var latestSettings = await this.settings.FindAllAsync();
// Refresh the settings cache.
try
{
this.settingsCacheLock.EnterWriteLock();
if (this.settingsCache != null)
{
//Notify settings changed
latestSettings.Except(this.settingsCache).ToList().ForEach(kv => this.changed.OnNext(kv));
}
this.settingsCache = latestSettings;
}
finally
{
this.settingsCacheLock.ExitWriteLock();
}
// Update the current version.
this.currentVersion = latestVersion;
}
catch (Exception ex)
{
this.changed.OnError(ex);
}
finally
{
this.syncCacheSemaphore.Release();
}
}
}
ExternalConfigurationManager类还提供了一个名为environment的属性。该属性支持在不同环境(如Staging和生产)中运行的应用程序进行不同配置。
ExternalConfigurationManager对象还可以定期查询BlobSettingsStore对象以执行更改请求。在下面的代码中,StartMonitor方法按指定时间间隔调用 CheckForConfigurationChanges 以检测任何更改事件, 如前面所述。
public class ExternalConfigurationManager : IDisposable
{
...
private readonly ISubject<KeyValuePair<string, string>> changed;
private Dictionary<string, string> settingsCache;
private readonly CancellationTokenSource cts = new CancellationTokenSource();
private Task monitoringTask;
private readonly TimeSpan interval;
private readonly SemaphoreSlim timerSemaphore = new SemaphoreSlim(1);
...
public ExternalConfigurationManager(string environment) : this(new BlobSettingsStore(environment), TimeSpan.FromSeconds(15), environment)
{
}
public ExternalConfigurationManager(ISettingsStore settings, TimeSpan interval, string environment)
{
this.settings = settings;
this.interval = interval;
this.CheckForConfigurationChangesAsync().Wait();
this.changed = new Subject<KeyValuePair<string, string>>();
this.Environment = environment;
}
...
/// <summary>
/// Check to see if the current instance is monitoring for changes
/// </summary>
public bool IsMonitoring => this.monitoringTask != null && !this.monitoringTask.IsCompleted;
/// <summary>
/// Start the background monitoring for configuration changes in the central store
/// </summary>
public void StartMonitor()
{
if (this.IsMonitoring)
return;
try
{
this.timerSemaphore.Wait();
// Check again to make sure we are not already running.
if (this.IsMonitoring)
return;
// Start running our task loop.
this.monitoringTask = ConfigChangeMonitor();
}
finally
{
this.timerSemaphore.Release();
}
}
/// <summary>
/// Loop that monitors for configuration changes
/// </summary>
/// <returns></returns>
public async Task ConfigChangeMonitor()
{
while (!cts.Token.IsCancellationRequested)
{
await this.CheckForConfigurationChangesAsync();
await Task.Delay(this.interval, cts.Token);
}
}
/// <summary>
/// Stop monitoring for configuration changes
/// </summary>
public void StopMonitor()
{
try
{
this.timerSemaphore.Wait();
// Signal the task to stop.
this.cts.Cancel();
// Wait for the loop to stop.
this.monitoringTask.Wait();
this.monitoringTask = null;
}
finally
{
this.timerSemaphore.Release();
}
}
public void Dispose()
{
this.cts.Cancel();
}
...
}
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 => configuredInstance.Value;
}
下面的代码取自ExternalConfigurationStore项目中的WorkerRole类。它显示了应用程序如何使用ExternalConfiguration 类来读取设置。
public override void Run()
{
// Start monitoring configuration changes.
ExternalConfiguration.Instance.StartMonitor();
// Get a setting.
var setting = ExternalConfiguration.Instance.GetAppSetting("setting1");
Trace.TraceInformation("Worker Role: Get setting1, value: " + setting);
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));
...
}
相关的模式和指导
GitHub(https://github.com/mspnp/cloud-design-patterns/tree/master/external-configuration-store)上提供了演示此模式的示例。