Använda geo-redundans för att utforma program med hög tillgänglighet

Molnbaserade infrastrukturer som Azure Storage ger en högtillgänglig och beständig plattform för värdhantering av data och program. Utvecklare av molnbaserade program måste noga överväga hur de ska utnyttja den här plattformen för att maximera dessa fördelar för sina användare. Azure Storage erbjuder alternativ för geo-redundans för att säkerställa hög tillgänglighet även under ett regionalt avbrott. Lagringskonton som konfigurerats för geo-redundant replikering replikeras synkront i den primära regionen och replikeras asynkront till en sekundär region som ligger hundratals mil bort.

Azure Storage erbjuder två alternativ för geo-redundant replikering: Geo-redundant lagring (GRS) och geozonredundant lagring (GZRS). Om du vill använda alternativen för geo-redundans i Azure Storage kontrollerar du att ditt lagringskonto är konfigurerat för geo-redundant lagring med läsbehörighet (RA-GRS) eller read-access geo-zone-redundant lagring (RA-GZRS). Om det inte är det kan du lära dig mer om hur du ändrar replikeringstypen för lagringskontot.

Den här artikeln visar hur du utformar ett program som fortsätter att fungera, om än i en begränsad kapacitet, även om det uppstår ett betydande avbrott i den primära regionen. Om den primära regionen blir otillgänglig kan ditt program växla sömlöst för att utföra läsåtgärder mot den sekundära regionen tills den primära regionen svarar igen.

Designöverväganden för program

Du kan utforma ditt program för att hantera tillfälliga fel eller betydande avbrott genom att läsa från den sekundära regionen när det finns ett problem som stör läsningen från den primära regionen. När den primära regionen är tillgänglig igen kan programmet återgå till läsning från den primära regionen.

Tänk på följande viktiga överväganden när du utformar ditt program för tillgänglighet och återhämtning med RA-GRS eller RA-GZRS:

  • En skrivskyddad kopia av de data som du lagrar i den primära regionen replikeras asynkront i en sekundär region. Den här asynkrona replikeringen innebär att den skrivskyddade kopian i den sekundära regionen så småningom överensstämmer med data i den primära regionen. Lagringstjänsten avgör platsen för den sekundära regionen.

  • Du kan använda Azure Storage-klientbiblioteken för att utföra läs- och uppdateringsbegäranden mot den primära regionens slutpunkt. Om den primära regionen inte är tillgänglig kan du automatiskt omdirigera läsbegäranden till den sekundära regionen. Du kan också konfigurera din app så att den skickar läsbegäranden direkt till den sekundära regionen, om så önskas, även när den primära regionen är tillgänglig.

  • Om den primära regionen blir otillgänglig kan du initiera en redundansväxling av ett konto. När du redundansväxlar till den sekundära regionen ändras DNS-posterna som pekar på den primära regionen så att de pekar på den sekundära regionen. När redundansväxlingen är klar återställs skrivåtkomsten för GRS- och RA-GRS-konton. Mer information finns i Haveriberedskap och redundansväxling av lagringskonto.

Arbeta med så småningom konsekventa data

Den föreslagna lösningen förutsätter att det är acceptabelt att returnera potentiellt inaktuella data till det anropande programmet. Eftersom data i den sekundära regionen så småningom är konsekventa är det möjligt att den primära regionen blir otillgänglig innan en uppdatering av den sekundära regionen har slutfört replikeringen.

Anta till exempel att kunden skickar en uppdatering, men att den primära regionen misslyckas innan uppdateringen sprids till den sekundära regionen. När kunden ber om att få läsa tillbaka data får de inaktuella data från den sekundära regionen i stället för uppdaterade data. När du utformar ditt program måste du bestämma om det här beteendet är acceptabelt eller inte. Om så är fallet måste du också överväga hur användaren ska meddelas.

Senare i den här artikeln får du lära dig mer om hantering av konsekventa data och hur du kontrollerar egenskapen Senaste synkroniseringstid för att utvärdera eventuella avvikelser mellan data i de primära och sekundära regionerna.

Hantera tjänster separat eller tillsammans

Även om det är osannolikt är det möjligt att en tjänst (blobar, köer, tabeller eller filer) blir otillgänglig medan de andra tjänsterna fortfarande fungerar fullt ut. Du kan hantera återförsöken för varje tjänst separat, eller så kan du hantera återförsök allmänt för alla lagringstjänster tillsammans.

Om du till exempel använder köer och blobar i ditt program kan du välja att lägga in separat kod för att hantera återförsöksbara fel för varje tjänst. På så sätt påverkar ett blobtjänstfel bara den del av programmet som hanterar blobar, vilket gör att köer fortsätter att köras som vanligt. Men om du bestämmer dig för att hantera alla återförsök för lagringstjänsten tillsammans påverkas begäranden till både blob- och kötjänster om någon av tjänsterna returnerar ett återförsöksbart fel.

I slutändan beror det här beslutet på programmets komplexitet. Du kanske föredrar att hantera fel per tjänst för att begränsa effekten av återförsök. Eller så kan du välja att omdirigera läsbegäranden för alla lagringstjänster till den sekundära regionen när du upptäcker ett problem med någon lagringstjänst i den primära regionen.

Köra programmet i skrivskyddat läge

För att effektivt förbereda för ett avbrott i den primära regionen måste ditt program kunna hantera både misslyckade läsbegäranden och misslyckade uppdateringsbegäranden. Om den primära regionen misslyckas kan läsbegäranden omdirigeras till den sekundära regionen. Uppdateringsbegäranden kan dock inte omdirigeras eftersom replikerade data i den sekundära regionen är skrivskyddade. Därför måste du utforma programmet så att det kan köras i skrivskyddat läge.

Du kan till exempel ange en flagga som kontrolleras innan några uppdateringsbegäranden skickas till Azure Storage. När en uppdateringsbegäran kommer kan du hoppa över begäran och returnera ett lämpligt svar till användaren. Du kan till och med välja att inaktivera vissa funktioner helt och hållet tills problemet har lösts och meddela användarna att funktionerna inte är tillgängliga för tillfället.

Om du bestämmer dig för att hantera fel för varje tjänst separat måste du också hantera möjligheten att köra programmet i skrivskyddat läge efter tjänst. Du kan till exempel konfigurera skrivskyddade flaggor för varje tjänst. Sedan kan du aktivera eller inaktivera flaggorna i koden efter behov.

Om du kan köra programmet i skrivskyddat läge kan du också säkerställa begränsade funktioner under en större programuppgradering. Du kan utlösa att programmet körs i skrivskyddat läge och peka på det sekundära datacentret, vilket säkerställer att ingen kommer åt data i den primära regionen när du gör uppgraderingar.

Hantera uppdateringar vid körning i skrivskyddat läge

Det finns många sätt att hantera uppdateringsbegäranden när de körs i skrivskyddat läge. Det här avsnittet fokuserar på några allmänna mönster att tänka på.

  • Du kan svara användaren och meddela dem att uppdateringsbegäranden inte bearbetas för närvarande. Ett kontakthanteringssystem kan till exempel göra det möjligt för användare att komma åt kontaktinformation men inte göra uppdateringar.

  • Du kan ange dina uppdateringar i en annan region. I det här fallet skulle du skriva dina väntande uppdateringsbegäranden till en kö i en annan region och sedan bearbeta dessa begäranden när det primära datacentret är online igen. I det här scenariot bör du meddela användaren att uppdateringsbegäran har placerats i kö för senare bearbetning.

  • Du kan skriva dina uppdateringar till ett lagringskonto i en annan region. När den primära regionen är online igen kan du koppla uppdateringarna till primära data, beroende på datastrukturen. Om du till exempel skapar separata filer med en datum/tid-stämpel i namnet kan du kopiera tillbaka filerna till den primära regionen. Den här lösningen kan gälla för arbetsbelastningar som loggning och IoT-data.

Hantera återförsök

Program som kommunicerar med tjänster som körs i molnet måste vara känsliga för oplanerade händelser och fel som kan uppstå. Dessa fel kan vara tillfälliga eller beständiga, allt från en tillfällig förlust av anslutningen till ett betydande avbrott på grund av en naturkatastrof. Det är viktigt att utforma molnprogram med lämplig återförsökshantering för att maximera tillgängligheten och förbättra den övergripande programstabiliteten.

Läsbegäranden

Om den primära regionen blir otillgänglig kan läsbegäranden omdirigeras till sekundär lagring. Som tidigare nämnts måste det vara acceptabelt för ditt program att potentiellt läsa inaktuella data. Azure Storage-klientbiblioteket erbjuder alternativ för att hantera återförsök och omdirigera läsbegäranden till en sekundär region.

I det här exemplet konfigureras återförsökshanteringen för Blob Storage i BlobClientOptions klassen och gäller för objektet BlobServiceClient vi skapar med hjälp av dessa konfigurationsalternativ. Den här konfigurationen är en primär och sekundär metod där återförsök av läsbegäranden från den primära regionen omdirigeras till den sekundära regionen. Den här metoden är bäst när fel i den primära regionen förväntas vara tillfälliga.

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);

Om du bedömer att den primära regionen sannolikt inte är tillgänglig under en längre tid kan du konfigurera alla läsbegäranden så att de pekar på den sekundära regionen. Den här konfigurationen är en sekundär metod . Som vi nämnde tidigare behöver du en strategi för att hantera uppdateringsbegäranden under den här tiden och ett sätt att informera användarna om att endast läsbegäranden bearbetas. I det här exemplet skapar vi en ny instans där BlobServiceClient slutpunkten för den sekundära regionen används.

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);

Att veta när du ska växla till skrivskyddat läge och sekundära begäranden är en del av ett arkitekturdesignmönster som kallas kretsbrytarmönstret, som kommer att diskuteras i ett senare avsnitt.

Uppdatera begäranden

Uppdateringsbegäranden kan inte omdirigeras till sekundär lagring, som är skrivskyddad. Som tidigare beskrivits måste ditt program kunna hantera uppdateringsbegäranden när den primära regionen inte är tillgänglig.

Kretsbrytarmönstret kan också tillämpas på uppdateringsbegäranden. Om du vill hantera fel med uppdateringsbegäran kan du ange ett tröskelvärde i koden, till exempel 10 på varandra följande fel, och spåra antalet fel för begäranden till den primära regionen. När tröskelvärdet har uppnåtts kan du växla programmet till skrivskyddat läge så att uppdateringsbegäranden till den primära regionen inte längre utfärdas.

Så här implementerar du kretsbrytarmönstret

Hantering av fel som kan ta en varierande mängd tid att återställa från är en del av ett arkitekturdesignmönster som kallas kretsbrytarmönstret. Korrekt implementering av det här mönstret kan förhindra att ett program upprepade gånger försöker utföra en åtgärd som sannolikt kommer att misslyckas, vilket förbättrar programmets stabilitet och återhämtning.

En aspekt av kretsbrytarmönstret är att identifiera när det finns ett pågående problem med en primär slutpunkt. För att göra detta kan du övervaka hur ofta klienten stöter på återförsöksbara fel. Eftersom varje scenario är olika måste du fastställa ett lämpligt tröskelvärde som ska användas för beslutet att växla till den sekundära slutpunkten och köra programmet i skrivskyddat läge.

Du kan till exempel välja att utföra växeln om det finns 10 på varandra följande fel i den primära regionen. Du kan spåra detta genom att hålla ett antal fel i koden. Om du lyckas innan du når tröskelvärdet anger du tillbaka antalet till noll. Om antalet når tröskelvärdet växlar du programmet så att det använder den sekundära regionen för läsbegäranden.

Som en alternativ metod kan du välja att implementera en anpassad övervakningskomponent i ditt program. Den här komponenten kan kontinuerligt pinga din primära lagringsslutpunkt med triviala läsbegäranden (till exempel läsa en liten blob) för att fastställa dess hälsa. Den här metoden skulle ta upp vissa resurser, men inte en betydande mängd. När ett problem identifieras som når tröskelvärdet växlar du till sekundära skrivskyddade begäranden och skrivskyddat läge. I det här scenariot kan du när du pingar den primära lagringsslutpunkten igen växla tillbaka till den primära regionen och fortsätta tillåta uppdateringar.

Det feltröskelvärde som används för att avgöra när växeln ska utföras kan variera från tjänst till tjänst i ditt program, så du bör överväga att göra dem till konfigurerbara parametrar.

Ett annat övervägande är hur du hanterar flera instanser av ett program och vad du ska göra när du identifierar återförsöksbara fel i varje instans. Du kan till exempel ha 20 virtuella datorer som körs med samma program inlästa. Hanterar du varje instans separat? Om en instans börjar få problem, vill du begränsa svaret till just den instansen? Eller vill du att alla instanser ska svara på samma sätt när en instans har problem? Det är mycket enklare att hantera instanserna separat än att försöka samordna svaret mellan dem, men din metod beror på programmets arkitektur.

Hantera så småningom konsekventa data

Geo-redundant lagring fungerar genom att replikera transaktioner från den primära till den sekundära regionen. Replikeringsprocessen garanterar att data i den sekundära regionen så småningom är konsekventa. Det innebär att alla transaktioner i den primära regionen så småningom visas i den sekundära regionen, men att det kan finnas en fördröjning innan de visas. Det finns inte heller någon garanti för att transaktioner kommer till den sekundära regionen i samma ordning som de ursprungligen tillämpades i den primära regionen. Om dina transaktioner kommer till den sekundära regionen i fel ordning kan du betrakta dina data i den sekundära regionen som inkonsekventa tills tjänsten kommer ikapp.

Följande exempel för Azure Table Storage visar vad som kan hända när du uppdaterar information om en anställd så att de blir medlem i administratörsrollen. För det här exemplet kräver detta att du uppdaterar medarbetarentiteten och uppdaterar en administratörsrollentitet med antalet administratörer. Observera hur uppdateringarna tillämpas i fel ordning i den sekundära regionen.

Tid Transaktion Replikering Senaste synkroniseringstid Resultat
T0 Transaktion A:
Infoga medarbetare
entitet i primär
Transaktion A infogad i primär,
har inte replikerats ännu.
T1 Transaktion A
replikeras till
Sekundära
T1 Transaktion A replikerad till sekundär.
Senaste synkroniseringstid uppdaterad.
T2 Transaktion B:
Uppdatera
medarbetarens entitet
i primärt
T1 Transaktion B skriven till primär,
har inte replikerats ännu.
T3 Transaktion C:
Uppdatera
administratör
rollentitet i
Primära
T1 Transaktion C skriven till primär,
har inte replikerats ännu.
T4 Transaktion C
replikeras till
Sekundära
T1 Transaktion C replikeras till sekundär.
LastSyncTime har inte uppdaterats eftersom
transaktion B har inte replikerats ännu.
T5 Läsa entiteter
från sekundär
T1 Du får det inaktuella värdet för medarbetare
entitet eftersom transaktion B inte har
har replikerats ännu. Du får det nya värdet för
administratörsrollentitet eftersom C har
Replikerade. Den senaste synkroniseringstiden har fortfarande inte
uppdaterats eftersom transaktion B
har inte replikerats. Du kan berätta för
administratörsrollentiteten är inkonsekvent
eftersom entitetens datum/tid är efter
senaste synkroniseringstid.
T6 Transaktion B
replikeras till
Sekundära
T6 T6 – Alla transaktioner via C har
replikerats, senaste synkroniseringstid
uppdateras.

I det här exemplet förutsätter vi att klienten växlar till att läsa från den sekundära regionen vid T5. Den kan läsa administratörsrollentiteten just nu, men entiteten innehåller ett värde för antalet administratörer som inte överensstämmer med antalet medarbetarentiteter som är markerade som administratörer i den sekundära regionen just nu. Klienten kan visa det här värdet, med risk för att informationen är inkonsekvent. Alternativt kan klienten försöka fastställa att administratörsrollen är i ett potentiellt inkonsekvent tillstånd eftersom uppdateringarna har inträffat i fel ordning och sedan informera användaren om detta.

För att avgöra om ett lagringskonto har potentiellt inkonsekventa data kan klienten kontrollera värdet för egenskapen Senaste synkroniseringstid . Senaste synkroniseringstid anger när data i den sekundära regionen senast var konsekventa och när tjänsten hade tillämpat alla transaktioner före den tidpunkten. I exemplet ovan, när tjänsten har infogat entiteten medarbetare i den sekundära regionen, är den senaste synkroniseringstiden inställd på T1. Den ligger kvar på T1 tills tjänsten uppdaterar entiteten medarbetare i den sekundära regionen när den är inställd på T6. Om klienten hämtar den senaste synkroniseringstiden när den läser entiteten på T5 kan den jämföra den med tidsstämpeln på entiteten. Om tidsstämpeln för entiteten är senare än den senaste synkroniseringstiden är entiteten i ett potentiellt inkonsekvent tillstånd och du kan vidta lämpliga åtgärder. Om du använder det här fältet måste du veta när den senaste uppdateringen av den primära slutfördes.

Information om hur du kontrollerar den senaste synkroniseringstiden finns i Kontrollera egenskapen Senaste synkroniseringstid för ett lagringskonto.

Testning

Det är viktigt att testa att programmet fungerar som förväntat när det påträffar försöksfel. Du måste till exempel testa att programmet växlar till den sekundära regionen när det upptäcker ett problem och sedan växlar tillbaka när den primära regionen blir tillgänglig igen. Om du vill testa det här beteendet korrekt behöver du ett sätt att simulera återförsöksbara fel och kontrollera hur ofta de inträffar.

Ett alternativ är att använda Fiddler för att fånga upp och ändra HTTP-svar i ett skript. Det här skriptet kan identifiera svar som kommer från din primära slutpunkt och ändra HTTP-statuskoden till en som storage-klientbiblioteket identifierar som ett nytt försöksfel. Det här kodfragmentet visar ett enkelt exempel på ett Fiddler-skript som fångar upp svar på läsbegäranden mot tabellen employeedata för att returnera statusen 502:

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

Du kan utöka det här exemplet för att fånga upp ett större antal begäranden och bara ändra responseCode på vissa av dem för att bättre simulera ett verkligt scenario. Mer information om hur du anpassar Fiddler-skript finns i Ändra en begäran eller ett svar i Fiddler-dokumentationen.

Om du har konfigurerat konfigurerbara tröskelvärden för att byta ditt program till skrivskyddat blir det enklare att testa beteendet med icke-produktionstransaktionsvolymer.


Nästa steg

Ett komplett exempel som visar hur du växlar fram och tillbaka mellan de primära och sekundära slutpunkterna finns i Azure Samples – Using the Circuit Breaker Pattern with RA-GRS storage (Azure Samples – Using the Circuit Breaker Pattern with RA-GRS storage).