Utilisez la géo-redondance pour concevoir des applications hautement disponibles

Les infrastructures basées sur le cloud telles que Stockage Azure fournissent une plateforme hautement disponible et durable pour l’hébergement des données et des applications. Les développeurs d’applications cloud doivent bien réfléchir à la façon de tirer parti de cette plateforme pour optimiser ces avantages pour leurs utilisateurs. Stockage Azure offre des options de géoredondance pour garantir une haute disponibilité, même en cas de panne régionale. Les comptes de stockage configurés pour la réplication géoredondante sont répliqués de manière synchrone dans la région primaire, puis répliqués de manière asynchrone dans une région secondaire à des centaines de kilomètres.

Stockage Azure offre deux options pour la réplication géoredondante : le stockage géoredondant (GRS) et le stockage géoredondant interzone (GZRS). Pour utiliser les options de géoredondance du Stockage Azure, vérifiez que votre compte de stockage est configuré pour le stockage géo-redondant avec accès en lecture (RA-GRS) ou le stockage géo-redondant interzone avec accès en lecture (RA-GZRS). Si ce n’est pas le cas, découvrez comment modifier le type de réplication de votre compte de stockage.

Cet article explique comment concevoir une application qui continuera à fonctionner, bien qu’en capacité limitée, même en cas de panne importante dans la région primaire. Si la région primaire devient indisponible, votre application peut basculer en toute transparence pour effectuer des opérations de lecture sur la région secondaire jusqu’à ce que la région primaire soit réactive à nouveau.

Considérations relatives à la conception d’applications

Vous pouvez configurer votre application pour gérer des problèmes temporaires ou des pannes significatives via la lecture à partir de la région secondaire lorsqu’un problème empêche la lecture à partir de la région principale. Lorsque la région principale redevient disponible, votre application peut reprendre la lecture à partir de la région principale.

Gardez à l’esprit ces considérations clés lors de la conception de votre application pour la disponibilité et la résilience à l’aide des options RA-GRS ou RA-GZRS :

  • Une copie en lecture seule des données que vous stockez dans la région primaire est répliquée de manière asynchrone dans une région secondaire. Cette réplication asynchrone signifie que la copie en lecture seule dans la région secondaire est finalement cohérente avec les données de la région primaire. Le service de stockage détermine l’emplacement de la région secondaire.

  • Vous pouvez utiliser les bibliothèques clientes Stockage Azure pour effectuer des demandes de lecture et de mise à jour par rapport au point de terminaison de la région primaire. Si la région primaire n’est pas disponible, vous pouvez rediriger automatiquement les demandes de lecture vers la région secondaire. Vous pouvez également configurer votre application pour qu’elle envoie les demandes de lecture directement à la région secondaire, si vous le souhaitez, même lorsque la région primaire est disponible.

  • Si la région primaire devient indisponible, vous pouvez initier un basculement de compte. Lorsque vous basculez vers la région secondaire, les entrées DNS pointant vers la région primaire sont modifiées pour pointer vers la région secondaire. Au terme du basculement, l'accès en écriture est restauré pour les comptes GRS et RA-GRS. Pour plus d’informations, consultez Récupération d’urgence et basculement de compte de stockage.

Utilisation de données éventuellement cohérentes

La solution proposée part du principe qu’il est acceptable de retourner à l’application appelante des données potentiellement périmées. Comme les données de la région secondaire finissent par être cohérentes, il est possible que la région principale devenir inaccessible avant qu’une mise à jour dans la région secondaire ait terminé la réplication.

Par exemple, votre client soumet une mise à jour avec succès, mais la région principale échoue avant la propagation de cette mise à jour à la région secondaire. Lorsque le client demande à relire les données, il reçoit les données périmées de la région secondaire au lieu des données mises à jour. Lorsque vous concevez votre application, vous devez décider si ce comportement est acceptable ou non. Si c’est le cas, vous devez également prendre en compte la façon d’avertir l’utilisateur.

Plus loin dans cet article, vous allez découvrir comment gérer des données éventuellement cohérentes et comment vérifier la propriété Heure de la dernière synchronisation pour évaluer les différences entre les données dans les régions primaires et secondaires.

Gestion des services ensemble ou séparément

Même si cette situation est peu probable, il est possible qu’un service (blobs, files d’attente, tables ou fichiers) devienne indisponible, alors que tous les autres restent entièrement fonctionnels. Vous pouvez gérer les nouvelles tentatives pour chaque service séparément ou gérer les nouvelles tentatives de façon générique pour l’ensemble des services de stockage.

Par exemple, si vous utilisez des files d’attente et des blobs dans votre application, vous pouvez décider d’insérer un code distinct pour gérer les erreurs renouvelables pour chaque service. Ainsi, une erreur du service blob affecte uniquement la partie de votre application qui traite des blobs, ce qui permet aux files d’attente de continuer à s’exécuter normalement. Toutefois, si vous décidez de gérer toutes les nouvelles tentatives de service de stockage ensemble, les demandes adressées aux services blob et de file d’attente seront affectées si l’un des services retourne une erreur renouvelable.

Cette décision dépend de la complexité de votre application. Vous préférerez peut-être gérer les défaillances par service pour limiter l’impact des nouvelles tentatives. Vous pouvez également décider de rediriger les demandes de lecture pour tous les services de stockage vers la région secondaire lorsque vous détectez un problème affectant tout service de stockage dans la région primaire.

Exécution de votre application en mode lecture seule

Pour préparer efficacement une interruption dans la région primaire, votre application doit être en mesure de gérer les demandes de lecture et de mise à jour ayant échoué. En cas d’échec de la région primaire, les demandes de lecture peuvent être redirigées vers la région secondaire. Toutefois, les demandes de mise à jour ne peuvent pas être redirigées, car les données répliquées dans la région secondaire sont en lecture seule. C’est la raison pour laquelle vous devez configurer votre application pour qu’elle s’exécute en mode lecture seule.

Par exemple, vous pouvez définir un indicateur qui est vérifié avant l’envoi de toute demande de mise à jour à Stockage Azure. Lorsqu’une demande de mise à jour aboutit, vous pouvez l’ignorer et retourner une réponse appropriée à l’utilisateur. Vous pouvez même décider de désactiver certaines fonctionnalités simultanément, et ce, jusqu’à ce que le problème soit résolu, et informer les utilisateurs que ces fonctionnalités sont temporairement indisponibles.

Si vous décidez de gérer les erreurs pour chaque service séparément, vous devez également gérer la possibilité d’exécuter votre application en mode lecture seule par service. Par exemple, vous pouvez configurer des indicateurs en lecture seule pour chaque service. Vous pouvez ensuite activer ou désactiver les indicateurs dans le code, si nécessaire.

La possibilité d’exécuter votre application en mode lecture seule offre aussi la possibilité d’assurer des fonctionnalités limitées pendant une mise à niveau majeure de l’application. Vous pouvez déclencher l’exécution de votre application en mode lecture seule et la faire pointer vers le centre de données secondaire. Vous avez ainsi la certitude que personne n’accède aux données dans la région primaire pendant vous effectuez des mises à niveau.

Gestion des mises à jour lors d’une exécution en mode lecture seule

Il existe de nombreuses façons de gérer les demandes de mise à jour lors d’une exécution en mode lecture seule. Cette section se concentre sur quelques modèles généraux à prendre en compte.

  • Vous pouvez répondre à l’utilisateur et l’avertir que les demandes de mise à jour ne sont pas traitées actuellement. Par exemple, un système de gestion des contacts pourrait permettre aux utilisateurs d’accéder aux informations de contact sans toutefois autoriser les mises à jour.

  • Vous pouvez empiler vos mises à jour dans une autre région. Dans ce cas, vous écrirez vos demandes de mise à jour en attente dans la file d’attente d’une autre région et disposerez d’un moyen de traiter ces demandes une fois le centre de données principal à nouveau en ligne. Dans ce scénario, vous devez informer l’utilisateur que la demande de mise à jour a été mise en file d’attente pour un traitement ultérieur.

  • Vous pouvez écrire vos mises à jour dans un compte de stockage d’une autre région. Lorsque la région primaire est de nouveau en ligne, vous pouvez fusionner ces mises à jour dans les données primaires, selon la structure des données. Par exemple, si vous créez des fichiers distincts, dont le nom contient l’horodatage, vous pouvez recopier ces fichiers dans la région primaire. Cette solution peut s’appliquer aux charges de travail telles que la journalisation et les données IoT.

Gestion des nouvelles tentatives

Les applications qui communiquent avec les services exécutés dans le cloud doivent être sensibles aux événements non planifiés et aux erreurs qui peuvent se produire. Ces erreurs peuvent être temporaires ou permanentes, allant d’une perte momentanée de connectivité à une panne importante due à une catastrophe naturelle. Il est important de concevoir des applications cloud avec une gestion appropriée des nouvelles tentatives pour optimiser la disponibilité et améliorer la stabilité globale des applications.

Demandes de lecture

Si la région primaire devient indisponible, les demandes de lecture peuvent être redirigées vers le stockage secondaire. Comme indiqué plus haut, la lecture de données périmées par votre application doit être acceptable. La bibliothèque de client Stockage Azure offre des options pour gérer les nouvelles tentatives et rediriger les demandes de lecture vers une région secondaire.

Dans cet exemple, la gestion des nouvelles tentatives pour le stockage Blob est configurée dans la classe BlobClientOptions et s’applique à l’objet BlobServiceClient que nous créons à l’aide de ces options de configuration. Cette configuration est une approche primaire puis secondaire, où les nouvelles tentatives de demande de lecture de la région primaire sont redirigées vers la région secondaire. Cette approche est optimale lorsque les défaillances dans la région primaire sont censées être temporaires.

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

Si vous déterminez que la région primaire est susceptible d’être indisponible pendant une longue période, vous pouvez configurer toutes les demandes de lecture pour qu’elles pointent vers la région secondaire. Cette configuration est une approche secondaire uniquement. Comme indiqué précédemment, vous aurez besoin d’une stratégie pour gérer les demandes de mise à jour pendant ce temps et d’un moyen d’informer les utilisateurs que seules les demandes de lecture sont traitées. Dans cet exemple, nous créons une nouvelle instance de BlobServiceClient qui utilise le point de terminaison de région secondaire.

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

Savoir quand basculer vers le mode lecture seule et les demandes secondaires uniquement fait partie d’un modèle de conception architecturale appelé modèle Disjoncteur, qui sera abordé dans une section ultérieure.

Demandes de mise à jour

Les demandes de mise à jour ne peuvent pas être redirigées vers un stockage secondaire, qui est en lecture seule. Comme décrit précédemment, votre application doit être en mesure de gérer les demandes de mise à jour lorsque la région primaire n’est pas disponible.

Le modèle Disjoncteur peut également être appliqué aux demandes de mise à jour. Pour gérer les erreurs de demande de mise à jour, vous pouvez définir un seuil dans le code, par exemple 10 échecs consécutifs, et suivre le nombre d’échecs pour les demandes vers la région primaire. Une fois le seuil atteint, vous pouvez basculer l’application en mode lecture seule afin que les demandes de mise à jour vers la région primaire ne soient plus émises.

Comment implémenter le modèle Disjoncteur

La gestion de défaillances après lesquelles la récupération peut prendre du temps fait partie d’un modèle de conception architecturale appelé Disjoncteur. Une implémentation appropriée de ce modèle peut empêcher une application de tenter à plusieurs reprises d’exécuter une opération susceptible d’échouer, ce qui améliore la stabilité et la résilience de l’application.

L’un des aspects du modèle Disjoncteur est d’identifier à quel moment il existe un problème en cours avec un point de terminaison principal. Pour ce faire, vous pouvez surveiller la fréquence à laquelle le client rencontre des erreurs renouvelables. Chaque scénario étant différent, vous devez décider du seuil à utiliser pour déterminer quand basculer vers le point de terminaison secondaire et exécuter l’application en mode lecture seule.

Par exemple, vous pouvez décider d’effectuer le basculement au bout de 10 échecs consécutifs dans la région primaire. Vous pouvez effectuer ce suivi en conservant le nombre d’échecs dans le code. S’il y a une réussite avant d’atteindre le seuil, définissez le nombre sur zéro. Si le nombre atteint le seuil, basculez l’application afin qu’elle utilise la région secondaire pour les demandes de lecture.

Ou vous pouvez décider d’implémenter un composant de supervision personnalisé dans votre application. Ce composant peut effectuer un ping continu sur votre point de terminaison de stockage principal avec des requêtes de lecture triviales (telles que la lecture d’un petit objet blob) pour déterminer son intégrité. Avec cette approche, la quantité de ressources sollicitées est raisonnable. Lorsque le système détecte qu’un problème atteint votre seuil, vous basculez vers les demandes de lecture secondaires uniquement et le mode lecture seule. Dans le cadre de ce scénario, lorsque les tests ping effectués sur le point de terminaison de stockage principal aboutissent de nouveau, vous pouvez basculer vers la région primaire et continuer à autoriser les mises à jour.

Le seuil d’erreurs, qui permet de déterminer le moment auquel effectuer le basculement, peut varier d’un service à l’autre dans votre application. Vous devez donc envisager de rendre ces paramètres configurables.

Vous devez également réfléchir à la façon de gérer plusieurs instances d’une application et à ce que vous souhaitez faire lorsque vous détectez des erreurs renouvelables dans chaque instance. Par exemple, vous pouvez avoir 20 machines virtuelles en cours d’exécution, sur lesquelles la même application est chargée. Gérez-vous chaque instance séparément ? Si une instance commence à rencontrer des problèmes, voulez-vous limiter la réponse à cette seule instance ? Ou voulez-vous que toutes les instances répondent de la même façon lorsqu’une instance a un problème ? Gérer les instances séparément est beaucoup plus simple que d’essayer de coordonner la réponse entre elles. Cependant, votre approche varie selon l’architecture de votre application.

Gestion des données cohérentes

Le stockage géoredondant réplique des transactions de la région primaire vers la région secondaire. Le processus de réplication garantit que les données de la région secondaire sont cohérentes. Cela signifie que toutes les transactions de la région primaire apparaîtront éventuellement dans la région secondaire, mais qu’il peut y avoir un délai avant qu’elles n'apparaissent. Il n’existe également aucune garantie que les transactions arrivent dans la région secondaire dans l’ordre dans lequel elles ont été appliquées à l’origine dans la région primaire. Si vos transactions arrivent dans la région secondaire dans le désordre, vous pouvez considérer que vos données dans cette région resteront dans un état incohérent jusqu’à ce que le service rattrape son retard.

L’exemple suivant de stockage de table Azure illustre ce qui peut se produire lorsque vous mettez à jour les informations d’un employé pour qu’il devienne un membre du rôle Administrateurs. Cet exemple implique que vous mettiez à jour l’entité d’employé et une entité de rôle administrateur avec le nombre total d’administrateurs. Notez la façon dont les mises à jour sont appliquées dans le désordre dans la région secondaire.

Time Transaction Réplication Dernière heure de synchronisation Résultat
T0 Transaction A :
Insérer l’entité d’employé
dans la région primaire
Transaction A insérée dans la région primaire,
pas encore répliquée.
T1 Transaction A
répliquée sur
la région secondaire
T1 Transaction A répliquée sur la région secondaire.
Dernière heure de synchronisation mise à jour.
T2 Transaction B :
Update
l’entité d’employé
dans la région primaire
T1 Transaction B écrite dans la région primaire,
pas encore répliquée.
T3 Transaction C :
Update
administrator
entité de rôle dans
primary
T1 Transaction C écrite dans la région primaire,
pas encore répliquée.
T4 Transaction C
répliquée sur
la région secondaire
T1 Transaction C répliquée sur la région secondaire.
LastSyncTime pas encore mis à jour car
la transaction B n’a pas encore été répliquée.
T5 Lire les entités
de la région secondaire
T1 Vous obtenez la valeur périmée pour l’entité d’employé
car la transaction B n’a
pas encore été répliquée. Vous obtenez la nouvelle valeur pour
l’entité de rôle d’administrateur car C a
été répliquée. La dernière heure de synchronisation n’a pas encore
été mise à jour car la transaction B
n’a pas été répliquée. Vous savez que
l’entité de rôle d’administrateur est cohérente
car l’heure/la date de l’entité sont postérieures à
la dernière heure de synchronisation.
T6 Transaction B
répliquée sur
la région secondaire
T6 T6 – Toutes les transactions jusqu’à C ont
été répliquées. La dernière heure de synchronisation
est mise à jour.

Dans cet exemple, supposons que le client bascule vers la lecture à partir de la région secondaire à l’instant T5. À ce stade, il peut lire correctement l’entité de rôle administrateur. Cependant, l’entité contient une valeur pour le nombre d’administrateurs qui n’est pas cohérente avec le nombre d’entités d’employé marquées comme administrateurs dans la région secondaire. Votre client pourrait afficher cette valeur, au risque que les informations soient incohérentes. Il pourrait également tenter de déterminer que le rôle administrateur est dans un état potentiellement incohérent dans la mesure où les mises à jour ont été effectuées dans le désordre et en informer l’utilisateur.

Pour déterminer si un compte de stockage a des données potentiellement incohérentes, le client peut vérifier la valeur de la propriété Dernière heure de synchronisation. Dernière heure de synchronisation vous indique la dernière heure à laquelle les données de la région secondaire étaient cohérentes et à laquelle le service avait appliqué toutes les transactions. Dans l’exemple ci-dessus, une fois que le service insère l’entité d’employé dans la région secondaire, la dernière heure de synchronisation est définie sur T1. Elle reste définie sur T1 jusqu’à ce que le service mette à jour l’entité d’employé dans la région secondaire, puis est définie sur T6. Si le client récupère la dernière heure de synchronisation lors de la lecture de l’entité à l’instant T5, il peut la comparer avec l’horodatage de l’entité. Si l’horodatage de l’entité est postérieur à la dernière heure de synchronisation, l’entité est dans un état potentiellement incohérent, et vous pouvez alors effectuer toute action appropriée. L’utilisation de ce champ requiert que vous sachiez à quel moment a été effectuée la dernière mise à jour de la région primaire.

Pour savoir comment vérifier l’heure de la dernière synchronisation, consultez Vérifier la propriété Heure de la dernière synchronisation pour un compte de stockage.

Test

Il est important de tester si votre application se comporte comme prévu lorsqu’elle rencontre des erreurs renouvelables. Par exemple, vous devez tester si l’application bascule vers la région secondaire lorsqu’elle détecte un problème et bascule de nouveau lorsque la région primaire est à nouveau disponible. Pour tester correctement ce comportement, il vous faut un moyen de simuler les erreurs renouvelables et de contrôler la fréquence à laquelle elles se produisent.

Une des options est d’utiliser Fiddler pour intercepter et modifier les réponses HTTP dans un script. Ce script peut identifier les réponses provenant de votre point de terminaison principal et remplacer le code d’état HTTP par un code que la bibliothèque cliente de stockage reconnaît comme une erreur renouvelable. Cet extrait de code offre un exemple simple de script Fiddler, qui intercepte les réponses aux demandes de lecture portant sur la table employeedata pour retourner un état 502 :

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

Vous pouvez étendre cet exemple pour intercepter un plus grand nombre de demandes et modifier le responseCode uniquement sur certaines d’entre elles pour mieux simuler un scénario réel. Pour plus d’informations sur la personnalisation des scripts Fiddler, consultez Modifying a Request or Response (Modification d’une demande ou d’une réponse) dans la documentation Fiddler.

Si vous avez défini des seuils configurables pour le basculement de votre application en mode lecture seule, il sera plus facile de tester le comportement avec des volumes de transaction hors production.


Étapes suivantes

Pour obtenir un exemple complet montrant comment effectuer les basculements entre les points de terminaison principaux et secondaires, consultez Azure Samples – Using the Circuit Breaker Pattern with RA-GRS storage (Exemples Azure – Utilisation du modèle Disjoncteur avec le stockage RA-GRS).