使用異地備援來設計高可用性應用程式

Azure 儲存體等雲端式基礎結構提供裝載資料和應用程式,高可用性且耐久的平台。 雲端式應用程式的開發人員必須仔細考慮如何利用此平台,讓其使用者發揮最大的優勢。 Azure 儲存體提供異地備援選項,確保區域中斷期間仍可提供高可用性。 針對異地備援複寫設定的儲存體帳戶,會以同步方式複寫至主要區域,然後以非同步方式複寫至數百英里外的次要區域。

Azure 儲存體提供異地備援複寫的兩個選項:異地備援儲存體 (GRS)異地區域備援儲存體 (GZRS)。 若要使用 Azure 儲存體異地備援選項,請確定您已針對讀取權限異地備援儲存體 (RA-GRS),或讀取權限異地區域備援儲存體 (RA-GZRS) 設定儲存體帳戶。 如果未設定,您可以深入了解如何變更儲存體帳戶複寫類型

本文說明如何設計即使容量有限,或主要區域發生重大中斷,仍可持續運作的應用程式。 如果無法使用主要區域,您的應用程式可以順暢切換為執行針對次要區域的讀取作業,直到主要區域再次回應。

應用程式設計考量

主要區域發生讀取干擾問題時,您可以設計應用程式從次要區域讀取,處理暫時性錯誤或重大中斷。 當主要區域再次可用時,您的應用程式可以回到從主要區域讀取。

請記得以下重要的考量,使用 RA-GRS 或 RA-GZRS,設計應用程式的可用性和復原:

  • 您儲存在主要區域的資料唯獨複本會以非同步方式複寫至次要區域。 此非同步複寫即次要區域中的唯讀複本最終會與主要區域中的資料一致。 儲存體服務決定次要區域的位置。

  • 您可以使用 Azure 儲存體用戶端程式庫,針對主要區域端點執行讀取或更新要求。 如果主要區域無法使用,您可以將讀取要求自動重新導向次要區域。 即使主要區域可以使用,您也可以視需要設定應用程式,直接傳送讀取要求至次要區域。

  • 如果主要區域無法使用,您可以進行帳戶容錯移轉。 您容錯移轉到次要區域時,指向主要區域的 DNS 項目會變更為指向次要區域。 完成容錯移轉之後,會還原 GRS 和 RA-GRS 帳戶的寫入權限。 如需詳細資訊,請參閱災害復原與儲存體帳戶容錯移轉

使用最終一致的資料

建議的解決方案假設帳戶接受將可能過時的資料傳回呼叫的應用程式。 鑑於次要區域中的資料是最終一致,次要區域完成複寫前,主要區域可能無法存取更新。

例如,假設您的客戶成功提交更新,但是在更新傳播到次要區域之前主要區域會失敗。 當客戶要求讀回資料時,會從次要區域接收到過時的資料,而非更新的資料。 設計應用程式時,您必須決定是否接受此行為。 如果是,您必須考慮如何通知使用者。

在本文稍後,您會深入了解處理最終一致的資料,及如何檢查上次同步時間屬性,並評估主要和次要區域的資料是否有任何差異。

個別處理服務或一併處理所有服務

雖然不太可能發生,但其他服務完全正常時,仍可能有服務 (blob、佇列、資料表或檔案) 無法使用。 您可以個別處理每個服務的重試,或以一般方式一併處理所有儲存體服務的重試。

例如,如果在應用程式中使用佇列和 blob,您可以決定放入不同程式碼,處理每個服務的可重試錯誤。 如此一來,blob 服務錯誤只會影響應用程式處理 blob 的部分,讓佇列繼續正常執行。 但您決定一併處理所有儲存體服務重試後,如果 blob 或佇列服務傳回可重試錯誤,即會影響針對兩個服務的要求。

您的決定最終取決於應用程式的複雜性。 您可能習慣依服務處理失敗,並限制重試的影響。 或在主要區域偵測到任何儲存體服務的問題時,您可能決定將所有儲存體服務的讀取要求重新導向次要區域。

在唯讀模式中執行您的應用程式

若要有效地為主要區域中斷做好準備,您的應用程式必須能同時處理失敗的讀取要求和失敗的更新要求。 如果主要區域失敗,可將讀取要求重新導向至次要區域。 但您無法重新導向更新要求,因為次要區域中的複寫資料是唯讀屬性。 基於這個理由,請設計可在唯讀模式中執行的應用程式。

例如,您可以設定旗標,在將任何更新要求提交到 Azure 儲存體之前先加以檢查。 當更新要求傳入時,您可以跳過要求,並傳回適當的回應給客戶。 而問題解決前,您甚至可以選擇完全停用特定功能,並通知使用者,暫時無法使用這些功能。

如果您決定個別處理每個服務的錯誤,也請處理應用程式透過服務在唯讀模式中執行的能力。 例如,您可以為每個服務設定唯讀旗標。 之後,您可以視需要啟用或停用程式碼中的旗標。

應用程式可以在唯讀模式中執行,也讓您可以在主要應用程式升級期間,確保有限的功能。 您可以觸發應用程式在唯讀模式中執行並指向次要資料中心,確保當您進行升級時,不會有人正在存取主要區域中的資料。

在唯讀模式中執行時處理更新

有許多方法可在唯讀模式中執行時處理更新要求。 本節著重於幾個要考慮的一般模式。

  • 您可以回應使用者並通知他們,目前未處理更新要求。 例如,連絡人管理系統讓使用者可以存取連絡人資訊,但無法更新。

  • 您可以將更新加入其他區域中的佇列。 在此案例中,您會將擱置中的更新要求寫入不同區域的佇列,然後在主要資料中心再次連線後,處理這些要求。 在此案例中,建議您讓使用者知道更新要求已排入佇列,以供稍後處理。

  • 您可以將更新寫入其他區域中的儲存體帳戶。 主要區域恢復連線後,您可以合併這些更新至主要資料 (取決於資料結構)。 例如,如果您建立名稱中包含日期/時間戳記的不同檔案,即可將這些檔案複製回主要區域。 此解決方案可以套用至記錄和 IoT 資料等工作負載。

處理重試

應用程式與雲端中執行的服務通訊時,必須靈敏發現可能發生的非計劃性事件和錯誤。 這些錯誤可能是暫時性或持續性,包含暫時性失去連線或自然災害導致的重大中斷。 請務必使用適當的重試處理設計雲端應用程式,將可用性最佳化,並改善整體應用程式的穩定性。

讀取要求

如果無法使用主要區域,您可以將讀取要求重新導向次要區域。 如前所述,您的應用程式必須可以接受可能讀取到過時的資料。 Azure 儲存體用戶端程式庫提供選項,處理重試及將讀取要求重新導向次要區域。

在此範例中,Blob 儲存體的重試處理是在 BlobClientOptions 類別中設定,並套用至使用這些設定選項建立的 BlobServiceClient 物件。 此設定是先主要後次要方法,即主要區域的讀取要求重試會重新導向次要區域。 此方法適用時機是預期主要區域中的失敗為暫時性。

string accountName = "<YOURSTORAGEACCOUNTNAME>";
Uri primaryAccountUri = new Uri($"https://{accountName}.blob.core.windows.net/");
Uri secondaryAccountUri = new Uri($"https://{accountName}-secondary.blob.core.windows.net/");

// Provide the client configuration options for connecting to Azure Blob storage
BlobClientOptions blobClientOptions = new BlobClientOptions()
{
    Retry = {
        // The delay between retry attempts for a fixed approach or the delay
        // on which to base calculations for a backoff-based approach
        Delay = TimeSpan.FromSeconds(2),

        // The maximum number of retry attempts before giving up
        MaxRetries = 5,

        // The approach to use for calculating retry delays
        Mode = RetryMode.Exponential,

        // The maximum permissible delay between retry attempts
        MaxDelay = TimeSpan.FromSeconds(10)
    },

    // If the GeoRedundantSecondaryUri property is set, the secondary Uri will be used for 
    // GET or HEAD requests during retries.
    // If the status of the response from the secondary Uri is a 404, then subsequent retries
    // for the request will not use the secondary Uri again, as this indicates that the resource 
    // may not have propagated there yet.
    // Otherwise, subsequent retries will alternate back and forth between primary and secondary Uri.
    GeoRedundantSecondaryUri = secondaryAccountUri
};

// Create a BlobServiceClient object using the configuration options above
BlobServiceClient blobServiceClient = new BlobServiceClient(primaryAccountUri, new DefaultAzureCredential(), blobClientOptions);

如果您判斷主要區域可能長時間無法使用,您可以設定所有讀取要求指向次要區域。 此設定是僅限次要方法。 如前所述,您需要策略處理這段期間的更新要求,及通知使用者目前只處理讀取要求的方式。 在此範例中,我們會建立 BlobServiceClient 的新執行個體,而此執行個體使用次要區域端點。

string accountName = "<YOURSTORAGEACCOUNTNAME>";
Uri primaryAccountUri = new Uri($"https://{accountName}.blob.core.windows.net/");
Uri secondaryAccountUri = new Uri($"https://{accountName}-secondary.blob.core.windows.net/");

// Create a BlobServiceClient object pointed at the secondary Uri
// Use blobServiceClientSecondary only when issuing read requests, as secondary storage is read-only
BlobServiceClient blobServiceClientSecondary = new BlobServiceClient(secondaryAccountUri, new DefaultAzureCredential(), blobClientOptions);

了解切換為唯讀模式和僅限次要要求的時機,是稱為斷路器模式的架構設計模式一部分,稍後章節會討論。

更新要求

更新要求無法重新導向唯讀的次要儲存體。 如前所述,主要區域無法使用時,您的應用程式必須可以處理更新要求

斷路器模式也適用於更新要求。 若要處理更新要求錯誤,您可以在程式碼中設定閾值 (例如 10 次連續失敗),並追蹤主要區域的要求失敗次數。 符合閾值後,您可以將應用程式切換為唯讀模式,不再對主要區域發出更新要求。

如何實作斷路器模式

處理可能需要長短不等的一段時間才能復原的失敗,是稱為斷路器模式的架構設計模式一部分。 此模式的正確實作可防止應用程式重複嘗試執行可能失敗的作業,進而改善應用程式的穩定性和復原能力。

斷路器模式的其中一個層面是,識別主要端點發生問題的時間。 若要執行此判定,您可以監視用戶端遇到可重試錯誤的頻率。 因為案例各有不同,您必須決定用來判斷切換為次要端點,及在唯讀模式中執行應用程式的適當閾值。

例如,如果主要區域有 10 次連續失敗,即可決定執行切換。 您可以透過記錄程式碼中的失敗計數,追蹤問題。 如果在觸達閾值前重試成功,請將計數重設為零。 如果計數觸達閾值,請將應用程式切換為使用次要區域讀取要求。

此外,您可以決定在應用程式中實作自訂監視元件,作為替代方法。 此元件可以使用簡單的讀取要求,持續偵測主要儲存體端點 (例如讀取小型 blob),並判斷該端點的健康情況。 此方法會占用部分資源,但時間不長。 發現有問題觸達閾值後,應用程式會切會為僅限次要讀取要求和唯讀模式。 以此案例而言,偵測主要儲存體端點再度變為成功後,即可切換回主要區域,並繼續允許更新。

用來判斷切換時機的錯誤閾值可能因應用程式中的服務而異,所以建議您考慮使用可設定的參數。

另一個考量是如何處理應用程式的多個執行個體,以及當您在每個執行個體中偵測到可重試的錯誤時該怎麼辦。 例如,您可能有 20 個 VM 正載入相同的應用程式來執行。 您要個別處理每個執行個體嗎? 如果有執行個體開始發生問題,您要僅限制回應該執行個體嗎, 或有執行個體發生問題時,您要所有執行個體以相同方式回應? 個別處理執行個體比嘗試在執行個體間協調回應簡單,而方法取決於您的應用程式結構。

處理最終一致的資料

異地備援儲存體的運作方式是將交易從主要區域複寫到次要區域。 複寫程序可保證次要區域中的資料最終會一致。 換句話說,主要區域中所有的交易最終會顯示在次要區域,只是顯示前可能有延隔時間。 此外,交易不保證會套用主要區域中原本的交易順序到達次要區域。 如果您的交易不按順序到達次要區域,則您可能要考慮讓次要區域中的資料處於不一致狀態,直到服務更新為止。

下列的 Azure 資料表儲存體範例示範,更新員工的詳細資料,並將他們設為管理員角色時,可能發生什麼情況。 基於此範例,這會要求您更新員工實體,並利用系統管理員總數的計數來更新系統管理員角色實體。 請注意,如何在次要區域中不按順序套用更新。

Time 交易 複寫 上次同步處理時間 結果
T0 交易 A:
會在主要區域中
插入員工實體
交易 A 已插入至主要區域,
但尚未複寫。
T1 交易 A
已複寫到
次要
T1 交易 A 已複寫到次要區域。
已更新上次同步處理時間。
T2 交易 B:
更新
主要區域中的
員工實體
T1 交易 B 已寫入主要區域,
但尚未複寫。
T3 交易 C:
更新
administrator
角色實體,位於
主要
T1 交易 C 已寫入主要區域,
但尚未複寫。
T4 交易 C
已複寫到
次要
T1 交易 C 已複寫到次要區域。
無法更新 LastSyncTime,因為
未複寫交易 B。
T5 從次要區域
讀取實體
T1 您取得員工實體的過時值,
因為交易 B 尚未
複寫。 您取得系統管理員角色實體
的新值,因為 C
已複寫。 上次同步處理時間仍然尚未
更新,因為交易 B
尚未複寫。 您可以說
系統管理員角色實體不一致,
因為實體日期/時間是在
上次同步處理時間之後。
T6 交易 B
已複寫到
次要
T6 T6 - 透過 C 的所有交易都
已複寫,上次同步處理時間
已更新。

在此範例中,假設用戶端會在 T5 從次要區域切換到讀取。 這時,用戶端可以在此時順利讀取管理員角色實體,但此實體包含管理員的計數值與員工實體數目不一致,而後者目前標示為次要區域中的管理員。 您的用戶端可能會顯示此值,同時顯示資訊不一致的風險。 或者,用戶端可能嘗試判斷系統管理員角色處於可能不一致的狀態,因為更新並未按順序進行,然後通知使用者此一事實。

若要判斷儲存體帳戶是否包含可能不一致的資料,用戶端可以檢查上次同步時間屬性的值。 上次同步時間告訴您次要區域中的資料上次保持一致的時間,及此時間點前服務套用所有交易的時間。 在上述範例中,當服務在次要區域中插入員工實體之後,就會將上次同步處理時間設為 T1。 同步時間會保持在 T1,直到時間設為 T6,且服務更新次要區域中的員工實體為止。 如果用戶端會在其讀取 T5 上的實體時擷取上次同步處理時間,就能與實體上的時間戳記進行比較。 如果實體上的時間戳記晚於上次同步時間,此實體可能處於不一致狀態,所以您可以採取適當的動作。 您必須知道上次對主要區域完成更新的時間,才能使用此欄位。

若要瞭解如何檢查上次同步時間,請參閱檢查儲存體帳戶的上次同步時間屬性

測試

請務必測試應用程式在發生可重試的錯誤時會如預期般運作。 例如,您需要測試應用程式偵測到問題時,是否切換至次要區域,然後在主要區域重新可以使用後,切換回來。 若要正確測試此行為,您需要方法模擬可重試的錯誤,並控制錯誤發生的頻率。

其中一個選項是,使用 Fiddler,攔截並修改指令碼中的 HTTP 回應。 此指令碼可以識別來自主要端點的回應,並將 HTTP 狀態碼變更為儲存體用戶端程式庫可辨識的可重試錯誤狀態碼。 此程式碼片段示範一個簡單的 Fiddler 指令碼範例,來攔截對 employeedata 資料表之讀取要求的回應,以傳回 502 狀態:

static function OnBeforeResponse(oSession: Session) {
    ...
    if ((oSession.hostname == "\[YOURSTORAGEACCOUNTNAME\].table.core.windows.net")
      && (oSession.PathAndQuery.StartsWith("/employeedata?$filter"))) {
        oSession.responseCode = 502;
    }
}

您可以擴充此範例,以攔截範圍較大的要求,而且只需變更其中的 responseCode,就能進一步模擬真實世界的案例。 如需自訂 Fiddler 指令碼的詳細資訊,請參閱 Fiddler 文件中的修改要求或回應 (英文)

如果您已設定閾值切換應用程式為唯讀,即可更輕易測試非生產交易量的行為。


下一步

如需示範如何在主要和次要端點之間來回切換的完整範例,請參閱 Azure 範例 - 搭配 RA-GRS 儲存體使用斷路器模式