# 云計算設計模式(八)——外部配置存儲模式
移動配置信息從應用部署包到一個集中位置。這個模式可以提供機會,以便管理和配置數據的控制,以及用于跨應用程序和應用程序實例共享的配置數據。
## 背景和問題
大多數應用程序運行時環境包括位于應用程序文件夾內的在部署應用程序文件保持配置信息。在某些情況下也能夠編輯這些文件來改變該應用程序的行為,它已經被部署之后。然而,在許多情況下,改變配置所需要的應用程序被重新部署,從而導致不可接受的停機時間和額外的管理開銷。
本地配置文件還配置限制為單個應用程序,而在某些情況下將是有用的,以在多個應用程序之間共享的配置設置。例子包括數據庫連接字符串,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));
...
}
~~~
- 前言
- (一)—— 緩存預留模式
- (二)—— 斷路器模式
- (三)—— 補償交易模式
- (四)——消費者的競爭模式
- (五)——計算資源整合模式
- (六)——命令和查詢職責分離(CQRS)模式
- (七)——事件獲取模式
- (八)——外部配置存儲模式
- (九)—— 聯合身份模式
- (十)——守門員模式
- (十一)—— 健康端點監控模式
- (十二)—— 索引表模式
- (十三)——領導人選舉模式
- (十四)——實體化視圖模式
- (十五)—— 管道和過濾器模式
- (十六)——優先級隊列模式
- (十七)—— 基于隊列的負載均衡模式
- (十八)—— 重試模式
- (十九)——運行重構模式
- (二十)—— 調度程序代理管理者模式
- (二十一)——Sharding 分片模式
- (二十二)——靜態內容托管模式
- (二十三)——Throttling 節流模式
- (二十四)—— 仆人鍵模式