Använda geo-redundans för att utforma program med hög tillgänglighet
En vanlig funktion i molnbaserade infrastrukturer som Azure Storage är att de tillhandahåller en mycket tillgänglig och beständig plattform för värdtjänster för 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 geo-redundant lagring för att säkerställa hög tillgänglighet även vid ett regionalt avbrott. Storage som konfigurerats för geo-redundant replikering replikeras synkront i den primära regionen och replikeras sedan asynkront till en sekundär region som ligger hundratals mil bort.
Azure Storage två alternativ för geo-redundant replikering. Den enda skillnaden mellan dessa två alternativ är hur data replikeras i den primära regionen:
Geo-zonredundant lagring (GZRS):Data replikeras synkront över tre Azure-tillgänglighetszoner i den primära regionen med zonredundant lagring (ZRS) och replikeras sedan asynkront till den sekundära regionen. För läsåtkomst till data i den sekundära regionen aktiverar du RA-GZRS (Read-Access Geo-Zone Redundant Storage).
Microsoft rekommenderar att du använder GZRS/RA-GZRS för scenarier som kräver maximal tillgänglighet och hållbarhet.
Geo-redundant lagring (GRS):Data replikeras synkront tre gånger i den primära regionen med hjälp av lokalt redundant lagring (LRS) och replikeras sedan asynkront till den sekundära regionen. För läsåtkomst till data i den sekundära regionen aktiverar du geo-redundant lagring med läsbehörighet (RA-GRS).
Den här artikeln visar hur du utformar ditt program för att hantera ett avbrott i den primära regionen. Om den primära regionen blir otillgänglig kan programmet anpassas för att utföra läsåtgärder mot den sekundära regionen i stället. Kontrollera att ditt lagringskonto har konfigurerats för RA-GRS eller RA-GZRS innan du börjar.
Designöverväganden för program vid läsning från den sekundära
Syftet med den här artikeln är att visa hur du utformar ett program som fortsätter att fungera (om än i begränsad kapacitet) även i händelse av en större katastrof i det primära datacentret. Du kan utforma ditt program för att hantera tillfälliga eller långvariga problem 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å dessa viktiga punkter när du utformar ditt program för RA-GRS eller RA-GZRS:
Azure Storage en skrivskyddade kopia av de data som du lagrar i din primära region i en sekundär region. Som nämnts ovan avgör lagringstjänsten platsen för den sekundära regionen.
Den skrivskyddade kopian överensstämmer så småningom med data i den primära regionen.
För blobar, tabeller och köer kan du fråga den sekundära regionen efter ett värde för Senaste synkroniseringstid som anger när den senaste replikeringen från den primära till den sekundära regionen inträffade. (Detta stöds inte för Azure Files, som för närvarande inte har RA-GRS-redundans.)
Du kan använda Storage klientbibliotek för att läsa och skriva data i antingen den primära eller sekundära regionen. Du kan också omdirigera läsbegäranden automatiskt till den sekundära regionen om en läsbegäran till den primära regionen tar slut.
Om den primära regionen blir otillgänglig kan du initiera en redundans för kontot. När du redundansar 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 redundansen är klar återställs skrivåtkomsten för GRS- och RA-GRS-konton. Mer information finns i Haveriberedskap och redundans för lagringskonto.
Använda slutligen 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örts.
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 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 detta är acceptabelt och i så fall hur du ska skicka ett meddelande till kunden.
Senare i den här artikeln visar vi hur du kontrollerar senaste synkroniseringstid för sekundära data för att kontrollera om den sekundära är uppdaterad.
Hantera tjänster separat eller helt tillsammans
Även om det är osannolikt är det möjligt att en tjänst blir otillgänglig medan de andra tjänsterna fortfarande är fullt funktionella. Du kan hantera återförsök och skrivskyddade läge för varje tjänst separat (blobar, köer, tabeller), 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 till separat kod för att hantera återförsöksbara fel för var och en av dessa. Om du sedan får ett nytt försök från blobtjänsten, men kötjänsten fortfarande fungerar, påverkas bara den del av programmet som hanterar blobar. Om du bestämmer dig för att hantera alla återförsök för lagringstjänsten allmänt och ett anrop till blobtjänsten returnerar ett återförsöksbart fel påverkas begäranden till både blobtjänsten och kötjänsten.
I slutändan beror detta på programmets komplexitet. Du kan välja att inte hantera felen per tjänst, utan i stället omdirigera läsbegäranden för alla lagringstjänster till den sekundära regionen och köra programmet i skrivskyddat läge när du upptäcker ett problem med alla lagringstjänster i den primära regionen.
Ytterligare överväganden
Det här är de andra överväganden som vi kommer att diskutera i resten av den här artikeln.
Hantera återförsök av läsbegäranden med hjälp av kretsbrytarmönstret
Slutligen konsekventa data och senaste synkroniseringstid
Testning
Köra programmet i skrivskyddade läge
För att effektivt förbereda för ett avbrott i den primära regionen måste du kunna hantera både misslyckade läsbegäranden och misslyckade uppdateringsbegäranden (med uppdatering i det här fallet infogningar, uppdateringar och borttagningar). Om den primära regionen misslyckas kan läsbegäranden omdirigeras till den sekundära regionen. Uppdateringsbegäranden kan dock inte omdirigeras till den sekundära eftersom den sekundära är skrivskyddad. Därför måste du utforma programmet så att det körs i skrivskyddade läge.
Du kan till exempel ange en flagga som kontrolleras innan eventuella uppdateringsbegäranden skickas till Azure Storage. När en av uppdateringsbegäranden kommer igenom kan du hoppa över den och returnera ett lämpligt svar till kunden. Du kanske till och med vill inaktivera vissa funktioner helt och hållet tills problemet har lösts och meddela användarna att dessa funktioner 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 ditt program i skrivskyddad läge efter tjänst. Du kan till exempel ha skrivskyddade flaggor för varje tjänst som kan aktiveras och inaktiveras. Sedan kan du hantera flaggan på lämpliga platser i koden.
Att kunna köra programmet i skrivskyddat läge har en annan fördel – det ger dig möjlighet att säkerställa begränsade funktioner under en större programuppgradering. Du kan utlösa att programmet körs i skrivskyddade läge och peka på det sekundära datacentret, vilket säkerställer att ingen kommer åt data i den primära regionen medan du uppgraderar.
Hantera uppdateringar när de körs i skrivskyddade läge
Det finns många sätt att hantera uppdateringsbegäranden när de körs i skrivskyddade läge. Vi kommer inte att gå igenom detta på ett omfattande sätt, men det finns i allmänhet ett par mönster som du överväger.
Du kan svara din användare och berätta att du för närvarande inte tar emot uppdateringar. Ett kontakthanteringssystem kan till exempel göra det möjligt för kunder att komma åt kontaktinformation men inte göra uppdateringar.
Du kan lägga dina uppdateringar i en annan region i en annan region. I det här fallet skulle du skriva 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 kunden att den begärda uppdateringen köas för senare bearbetning.
Du kan skriva dina uppdateringar till ett lagringskonto i en annan region. När det primära datacentret är online igen kan du sedan sammanslå uppdateringarna till primära data, beroende på datastrukturen. Om du till exempel skapar separata filer med en datum-/tidsstämpel i namnet kan du kopiera filerna tillbaka till den primära regionen. Detta fungerar för vissa arbetsbelastningar, till exempel loggning och iOT-data.
Hantera återförsök
Med Azure Storage klientbiblioteket kan du avgöra vilka fel som kan göras igen. Ett 404-fel (resursen hittades inte) skulle till exempel inte försökas igen eftersom det inte är troligt att det lyckas om du försöker igen. Å andra sidan kan ett 500-fel göras på nytt eftersom det är ett serverfel, och problemet kan bara vara ett tillfälligt problem. Mer information finns i den öppna källkoden för klassen ExponentialRetry i .NET-lagringsklientbiblioteket. (Leta efter metoden ShouldRetry.)
Läsbegäranden
Läsbegäranden kan omdirigeras till sekundär lagring om det uppstår problem med den primära lagringen. Som nämnts ovan i Använda eventuellt konsekventa datamåste det vara acceptabelt för ditt program att potentiellt läsa inaktuella data. Om du använder lagringsklientbiblioteket för att komma åt data från den sekundära, kan du ange återförsöksbeteendet för en läsbegäran genom att ange ett värde för egenskapen LocationMode till något av följande:
PrimaryOnly (standard)
PrimaryThenSecondary
SecondaryOnly
SecondaryThenPrimary
När du ställer in LocationMode på PrimaryThenSecondary, och den första läsbegäran till den primära slutpunkten misslyckas med ett fel som kan försökas igen, gör klienten automatiskt en ny läsbegäran till den sekundära slutpunkten. Om felet är en tidsgräns för servern måste klienten vänta tills tidsgränsen går ut innan den får ett återförsöksbart fel från tjänsten.
Det finns i princip två scenarier att tänka på när du bestämmer hur du ska svara på ett återförsöksbart fel:
Det här är ett isolerat problem och efterföljande begäranden till den primära slutpunkten returnerar inte ett återförsöksbart fel. Ett exempel på var detta kan inträffa är när det uppstår ett tillfälligt nätverksfel.
I det här scenariot finns det ingen betydande prestandaförseelse i att ha LocationMode inställt på PrimaryThenSecondary eftersom detta bara inträffar sällan.
Det här är ett problem med minst en av lagringstjänsterna i den primära regionen och alla efterföljande begäranden till tjänsten i den primära regionen returnerar troligen återförsöksbara fel under en viss tidsperiod. Ett exempel på detta är om den primära regionen är helt otillgänglig.
I det här scenariot finns det en prestandaförsening eftersom alla dina läsbegäranden försöker med den primära slutpunkten först, väntar på att tidsgränsen upphör att gälla och sedan växlar till den sekundära slutpunkten.
I dessa scenarier bör du identifiera att det finns ett pågående problem med den primära slutpunkten och skicka alla läsbegäranden direkt till den sekundära slutpunkten genom att ange egenskapen LocationMode till SecondaryOnly. Just nu bör du också ändra programmet så att det körs i skrivskyddade läge. Den här metoden kallas kretsbrytermönstret.
Uppdatera begäranden
Kretsbrytarmönstret kan också tillämpas på uppdateringsbegäranden. Uppdateringsbegäranden kan dock inte omdirigeras till sekundär lagring, som är skrivskyddad. För dessa begäranden bör du lämna egenskapen LocationMode inställd på PrimaryOnly (standard). För att hantera de här felen kan du använda ett mått för dessa begäranden – till exempel 10 fel på en rad – och när tröskelvärdet uppfylls växlar du programmet till skrivskyddade läge. Du kan använda samma metoder för att återgå till uppdateringsläge som de som beskrivs nedan i nästa avsnitt om kretsbrytarmönstret.
Strömbrytarmönstret
Om du använder kretsbrytarmönstret i ditt program kan det förhindra att det försöker utföra en åtgärd som troligen kommer att misslyckas upprepade gånger. Det gör att programmet kan fortsätta att köras i stället för att ta upp tid medan åtgärden utförs exponentiellt. Den identifierar också när felet har åtgärdats, då programmet kan försöka åtgärden igen.
Så här implementerar du kretsbrytermönstret
För att identifiera att det finns ett pågående problem med en primär slutpunkt kan du övervaka hur ofta klienten stöter på återförsöksbara fel. Eftersom varje ärende är olika måste du bestämma vilket tröskelvärde du vill använda för beslutet att växla till den sekundära slutpunkten och köra programmet i skrivskyddade läge. Du kan till exempel välja att utföra växeln om det finns 10 fel på en rad utan lyckade åtgärder. Ett annat exempel är att växla om 90 % av begärandena under en 2-minutersperiod misslyckas.
I det första scenariot kan du helt enkelt behålla antalet fel, och om det lyckas innan du når maxvärdet anger du tillbaka antalet till noll. För det andra scenariot är ett sätt att implementera det att använda MemoryCache-objektet (i .NET). Lägg till en CacheItem i cacheminnet för varje begäran, ange värdet till lyckades (1) eller misslyckat (0) och ange förfallotiden till 2 minuter från nu (eller vad din tidsbegränsning är). När en posts förfallotid nås tas posten bort automatiskt. Då visas ett rullande fönster på 2 minuter. Varje gång du gör en begäran till lagringstjänsten använder du först en Linq-fråga i objektet MemoryCache för att beräkna antalet lyckade resultat genom att summera värdena och dividera med antalet. När procentandelen lyckas understiger ett tröskelvärde (till exempel 10 %) anger du egenskapen LocationMode för läsbegäranden till SecondaryOnly och växlar programmet till skrivskyddat läge innan du fortsätter.
Tröskelvärdet för fel som används för att avgöra när växlingen ska göras kan variera från tjänst till tjänst i ditt program, så du bör överväga att göra dem konfigurerbara parametrar. Det är också här du bestämmer dig för att hantera återförsöksbara fel från varje tjänst separat eller som ett, enligt tidigare diskussion.
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äst. Hanterar du varje instans separat? Om en instans börjar få problem, vill du begränsa svaret till bara den instansen, eller vill du försöka få alla instanser att 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 hur du gör det beror på programmets arkitektur.
Alternativ för att övervaka felfrekvensen
Du har tre huvudalternativ för att övervaka frekvensen för återförsök i den primära regionen för att avgöra när du ska växla över till den sekundära regionen och ändra programmet så att det körs i skrivskyddade läge.
Lägg till en hanterare för retrying-händelsen för operationContext-objektet som du skickar till dina lagringsbegäranden – det här är den metod som visas i den här artikeln och som används i det tillhörande exemplet. Dessa händelser inträffar när klienten försöker skicka en begäran igen, så att du kan spåra hur ofta klienten påträffar återförsöksbara fel på en primär slutpunkt.
Vi arbetar för närvarande med att skapa kodfragment som återspeglar version 12.x Azure Storage klientbibliotek. Mer information finns i Announcing the Azure Storage v12 Client Libraries.
I metoden Evaluate in a custom retry policy (Utvärdera metod i en anpassad återförsöksprincip) kan du köra anpassad kod när ett nytt försök äger rum. Förutom att registrera när ett nytt försök inträffar, ger detta dig också möjlighet att ändra återförsöksbeteendet.
Vi arbetar för närvarande med att skapa kodfragment som återspeglar version 12.x Azure Storage klientbibliotek. Mer information finns i Announcing the Azure Storage v12 Client Libraries.
Den tredje metoden är att implementera en anpassad övervakningskomponent i ditt program som kontinuerligt pingar den primära lagringsslutpunkten med dummyläsningsbegäranden (till exempel läsning av en liten blob) för att fastställa dess hälsa. Detta skulle ta upp vissa resurser, men inte en betydande mängd. När ett problem identifieras som når tröskelvärdet utför du sedan växlingen till SecondaryOnly och skrivskyddat läge.
Vid något tillfälle bör du växla tillbaka till den primära slutpunkten och tillåta uppdateringar. Om du använder någon av de två första metoderna som anges ovan kan du helt enkelt växla tillbaka till den primära slutpunkten och aktivera uppdateringsläget efter en godtyckligt vald tid eller antalet åtgärder har utförts. Du kan sedan låta den gå igenom logiken för omförsök igen. Om problemet har åtgärdats fortsätter det att använda den primära slutpunkten och tillåta uppdateringar. Om det fortfarande finns ett problem växlar det återigen tillbaka till den sekundära slutpunkten och skrivskyddat läge efter att de kriterier som du har angett har misslyckats har misslyckats.
I det tredje scenariot, när pingning av den primära lagringsslutpunkten lyckas igen, kan du utlösa växlingen tillbaka till PrimaryOnly och fortsätta att tillåta uppdateringar.
Hantera slutligen konsekventa data
Geo-redundant lagring fungerar genom att replikera transaktioner från den primära till den sekundära regionen. Den här 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 uppstå en fördröjning innan de visas och att det inte finns någon garanti för att transaktionerna kommer in i den sekundära regionen i samma ordning som de ursprungligen tillämpades i den primära regionen. Om dina transaktioner tas emot i den sekundära regionen i fel ordning kan du betrakta dina data i den sekundära regionen som i ett inkonsekvent tillstånd tills tjänsten ikapp.
I följande tabell visas ett exempel på vad som kan hända när du uppdaterar informationen om en medarbetare så att den blir medlem i administratörsrollen. För det här exemplet kräver detta att du uppdaterar medarbetarens entitet och uppdaterar en administratörsrollentitet med ett antal av det totala antalet administratörer. Observera hur uppdateringarna tillämpas i ordningsföljd 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, inte replikerat ännu. |
||
| T1 | Transaktion A replikeras till Sekundära |
T1 | Transaktion A replikerad till sekundär. Senaste synkroniseringstid har uppdaterats. |
|
| T2 | Transaktion B: Uppdatera medarbetarentitet i primär |
T1 | Transaktion B skrivs till primär, inte replikerat ännu. |
|
| T3 | Transaktion C: Uppdatera administratör rollentitet i Primära |
T1 | Transaktion C skrivs till primär, inte replikerat ä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 medarbetaren entitet eftersom transaktion B inte har gjort det replikerats ännu. Du får det nya värdet för administratörsrollentitet eftersom C har Replikerade. Senaste synkroniseringstid har fortfarande inte har uppdaterats eftersom transaktion B har inte replikerats. Du kan se till att administratörsrollentiteten är inkonsekvent eftersom entitetens datum/tid är efter senaste synkroniseringen. |
|
| T6 | Transaktion B replikeras till Sekundära |
T6 | T6 – Alla transaktioner via C har replikerats, senaste synkroniseringstid uppdateras. |
I det här exemplet antar vi att klienten växlar till läsning från den sekundära regionen vid T5. Den kan nu läsa entiteten administratörsroll, men entiteten innehåller ett värde för antalet administratörer som inte stämmer överens med antalet medarbetaretiteter som har markerats som administratörer i den sekundära regionen just nu. Din klient kan helt enkelt visa det här värdet, med risken att det är inkonsekvent information. Alternativt kan klienten försöka fastställa att administratörsrollen är i ett potentiellt inkonsekvent tillstånd eftersom uppdateringarna har skett i fel ordning och sedan informera användaren om detta.
För att identifiera att det finns potentiellt inkonsekventa data kan klienten använda värdet för den senaste synkroniseringstiden som du kan få när som helst genom att fråga en lagringstjänst. Detta 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 medarbetarens entitet i den sekundära regionen, ställs den senaste synkroniseringstiden in på T1. Den förblir på T1 tills tjänsten uppdaterar medarbetarens entitet 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 vid T5 kan den jämföra den med tidsstämpeln för 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 för ditt program. Om du använder det här fältet måste du veta när den senaste uppdateringen av den primära har slutförts.
Information om hur du kontrollerar tiden för senaste synkronisering 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 stöter på återförsöksbara fel. Du måste till exempel testa att programmet växlar till det sekundära och skrivskyddade läget när det identifierar ett problem och växlar tillbaka när den primära regionen blir tillgänglig igen. För att göra detta behöver du ett sätt att simulera återförsöksbara fel och kontrollera hur ofta de inträffar.
Du kan 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 återförsöksbart fel. Det här kodfragmentet visar ett enkelt exempel på ett Fiddler-skript som fångar upp svar på läsbegäranden mot employeedata-tabellen för att returnera statusen 502:
Vi arbetar för närvarande med att skapa kodfragment som återspeglar version 12.x Azure Storage klientbibliotek. Mer information finns i Announcing the Azure Storage v12 Client Libraries.
Nästa steg
Ett fullständigt 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-exempel – Använda kretsbrytarmönstret med RA-GRS-lagring).