Uso de redundancia geográfica para diseñar aplicaciones de alta disponibilidad

Las infraestructuras basadas en la nube como Azure Storage proporcionan una plataforma de alta disponibilidad y duración para hospedar datos y aplicaciones. Los desarrolladores de aplicaciones basadas en la nube deben considerar atentamente cómo aprovechar esta plataforma para maximizar estas ventajas para sus usuarios. Azure Storage ofrece opciones con redundancia geográfica para garantizar la alta disponibilidad incluso durante una interrupción regional. Las cuentas de almacenamiento configuradas para la replicación con redundancia geográfica se replican de forma sincrónica en la región primaria y se replican de forma asincrónica en una región secundaria que se encuentra a cientos de kilómetros de distancia.

Azure Storage ofrece dos opciones para la replicación con redundancia geográfica: almacenamiento con redundancia geográfica (GRS) y almacenamiento con redundancia de zona geográfica (GZRS). Para usar las opciones de redundancia geográfica de Azure Storage, asegúrese de que la cuenta de almacenamiento está configurada para el almacenamiento con redundancia geográfica con acceso de lectura (RA-GRS) o el almacenamiento con redundancia de zona geográfica con acceso de lectura (RA-GZRS). Si no es así, puede obtener más información sobre cómo cambiar el tipo de replicación de la cuenta de almacenamiento.

En este artículo se muestra cómo diseñar una aplicación que seguirá funcionando, aunque en una capacidad limitada, incluso cuando haya una interrupción significativa en la región primaria. Si la región primaria deja de estar disponible, la aplicación puede cambiar sin problemas para realizar operaciones de lectura en la región secundaria hasta que la región primaria vuelva a responder.

Consideraciones sobre el diseño de aplicaciones

Puede diseñar la aplicación para que se enfrente a errores transitorios o interrupciones prolongadas pasando a leer desde la región secundaria cuando se produzca un problema que interfiera con la lectura desde la región primaria. Cuando la región primaria vuelva a estar disponible, la aplicación podrá volver a leer desde ella.

Tenga en cuenta estas consideraciones clave al diseñar su aplicación por lo que respecta a la disponibilidad y resistencia al usar RA-GRS o RA-GZRS:

  • Una copia de solo lectura de los datos que almacena en la región primaria se replica de forma asincrónica en una región secundaria. Esta replicación asincrónica significa que la copia de solo lectura de la región secundaria es finalmente coherente con los datos de la región primaria. El servicio de almacenamiento determina la ubicación de la región secundaria.

  • Puede usar las bibliotecas cliente de Azure Storage para realizar solicitudes de lectura y actualización en el punto de conexión de la región primaria. Si la región primaria no está disponible, puede redirigir automáticamente las solicitudes de lectura a la región secundaria. También puede configurar la aplicación para enviar solicitudes de lectura directamente a la región secundaria, si lo desea, incluso cuando la región primaria esté disponible.

  • Si la región primaria deja de estar disponible, puede iniciar una conmutación por error de la cuenta. Si la conmutación por error tiene como destino la región secundaria, las entradas DNS que apunten a la región primaria se modificarán para que apunten a la región secundaria. Una vez completada la conmutación por error, se restaurará el acceso de escritura en las cuentas GRS y RA-GRS. Para obtener más información, consulte Recuperación ante desastres y conmutación por error de la cuenta de almacenamiento.

Trabajar con datos coherentes con el tiempo

En la solución propuesta se da por supuesto que es aceptable devolver datos potencialmente obsoletos a la aplicación que realiza la llamada. Debido a que, en última instancia, los datos de la región secundaria son coherentes, puede que no sea posible acceder a la región primaria antes de que una actualización en la región secundaria haya terminado de replicarse.

Por ejemplo, imagine que el cliente envía correctamente una actualización, pero se produce un error en la región primaria antes de que la actualización se propague a la región secundaria. Cuando el cliente pide leer de nuevo los datos, se reciben los datos obsoletos de la región secundaria en lugar de los datos actualizados. Al diseñar la aplicación, debe decidir si este comportamiento es aceptable o no. Si lo es, también debe tener en cuenta cómo notificar al usuario.

Más adelante en este artículo, obtendrá más información sobre cómo controlar los datos coherentes finalmente y cómo comprobar la propiedad Hora de la última sincronización para evaluar las discrepancias entre los datos de las regiones primaria y secundaria.

Control de servicios por separado o conjuntamente

Aunque no es probable, es posible que un servicio (blobs, colas, tablas o archivos) deje de estar disponible mientras que los demás sigan siendo totalmente funcionales. Puede controlar los reintentos para cada servicio por separado o puede controlar los reintentos de forma genérica para todos los servicios de almacenamiento en conjunto.

Por ejemplo, si usa colas y blobs en la aplicación, puede decidir colocarlos de forma independiente en el código para poder controlar los errores con posibilidad de reintento para cada servicio. De este modo, un error de Blob service solo afectará a la parte de la aplicación que se ocupa de los blobs, lo que deja que las colas sigan ejecutándose de la forma normal. Sin embargo, si decide controlar todos los reintentos del servicio de almacenamiento juntos, las solicitudes a los servicios de blobs y colas se verán afectadas si alguno de los servicios devuelve un error que se puede reintentar.

En última instancia, esta decisión depende de la complejidad de la aplicación. Es posible que prefiera controlar los errores por servicio para limitar el impacto de los reintentos. O es posible que decida redirigir las solicitudes de lectura para todos los servicios de almacenamiento a la región secundaria cuando se detecte un problema con cualquier servicio de almacenamiento en la región primaria.

Ejecución de la aplicación en modo de solo lectura

Para prepararse de forma eficaz para una interrupción en la región primaria, su aplicación debe ser capaz de controlar tanto solicitudes de lectura con errores como solicitudes de actualización con errores. Si se produce un error en la región primaria, se pueden redirigir las solicitudes de lectura a la región secundaria. Sin embargo, las solicitudes de actualización no se pueden redirigir porque los datos replicados en la región secundaria son de solo lectura. Por este motivo, debe diseñar la aplicación para que se pueda ejecutar en modo de solo lectura.

Por ejemplo, puede establecer una marca que se comprueba antes de que se envíe cualquier solicitud de actualización a Azure Storage. Cuando llegue una solicitud de actualización, puede pasarla por alto y devolver una respuesta adecuada al cliente. Incluso es posible que escoja deshabilitar totalmente determinadas características hasta que se resuelva el problema y notificar a los usuarios que las características no están disponibles temporalmente.

Si decide controlar errores para cada servicio por separado, también necesitará controlar la capacidad de ejecutar la aplicación en modo de solo lectura para cada servicio. Por ejemplo, puede configurar marcas de solo lectura para cada servicio. Después, puede habilitar o deshabilitar las marcas en el código, según sea necesario.

Poder ejecutar la aplicación en modo de solo lectura también tiene la capacidad de garantizar una funcionalidad limitada durante una actualización importante de la aplicación. Puede desencadenar la aplicación para que se ejecute en modo de solo lectura y apunte al centro de datos secundario, de forma que nadie acceda a los datos de la región primaria mientras se estén llevando a cabo actualizaciones.

Control de las actualizaciones durante la ejecución en modo de solo lectura

Existen muchas formas de controlar solicitudes de actualización durante la ejecución en modo de solo lectura. Esta sección se centra en algunos patrones generales que se deben tener en cuenta.

  • Puede responder al usuario y notificarle que las solicitudes de actualización no se están procesando actualmente. Por ejemplo, un sistema de administración de contactos podría permitir que los usuarios accedan a información de contacto, pero no que la actualicen.

  • Puede poner las actualizaciones en cola en otra región. En este caso, podría escribir las solicitudes de actualización pendientes en una cola de una región distinta y, después procesar esas solicitudes una vez que el centro de datos principal vuelva a estar en línea. En este escenario, debería hacer saber al usuario que la solicitud de actualización está en cola para procesarse más adelante.

  • Puede escribir las actualizaciones en una cuenta de almacenamiento de otra región. Cuando la región primaria vuelva a estar en línea, puede combinar esas actualizaciones con los datos principales, en función de la estructura de estos. Por ejemplo, si va a crear archivos distintos con una marca de fecha y hora en el nombre, puede copiar esos archivos de nuevo a la región primaria. Esta solución se puede aplicar a cargas de trabajo como el registro y los datos de IoT.

Control de los reintentos

Las aplicaciones que se comunican con los servicios que se ejecutan en la nube deben ser sensibles a eventos y errores no planeados que pueden producirse. Estos errores pueden ser transitorios o persistentes, que van desde una pérdida momentánea de conectividad a una interrupción significativa debido a un desastre natural. Es importante diseñar aplicaciones en la nube con el control de reintento adecuado para maximizar la disponibilidad y mejorar la estabilidad general de las aplicaciones.

Solicitudes de lectura

Si la región primaria deja de estar disponible, se pueden redirigir las solicitudes de lectura al almacenamiento secundario. Como se indicó antes, debe ser aceptable que su aplicación lea datos potencialmente obsoletos. La biblioteca cliente de Azure Storage ofrece opciones para controlar los reintentos y redirigir las solicitudes de lectura a una región secundaria.

En este ejemplo, el control de reintentos para Blob Storage se configura en la clase BlobClientOptions y se aplicará al objeto BlobServiceClient que creamos mediante estas opciones de configuración. Esta configuración es un enfoque principal y secundario, donde los reintentos de solicitud de lectura de la región primaria se redirigen a la región secundaria. Este enfoque es mejor cuando se espera que los errores de la región primaria sean temporales.

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 determina que es probable que la región primaria no esté disponible durante un largo período de tiempo, puede configurar todas las solicitudes de lectura para que apunten a la región secundaria. Esta configuración es un enfoque solo secundario. Como se ha explicado anteriormente, necesitará una estrategia para controlar las solicitudes de actualización durante este tiempo y una manera de informar a los usuarios de que solo se están procesando las solicitudes de lectura. En este ejemplo, se crea una nueva instancia de BlobServiceClient que usa el punto de conexión de la región secundaria.

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

Saber cuándo cambiar al modo de solo lectura y las solicitudes solo secundarias forman parte de un patrón de diseño arquitectónico denominado patrón disyuntor, que se describirá en una sección posterior.

Solicitudes de actualización

Las solicitudes de actualización no se pueden redirigir al almacenamiento secundario, que es de solo lectura. Como se ha descrito anteriormente, la aplicación debe poder controlar las solicitudes de actualización cuando la región primaria no está disponible.

El patrón de disyuntor también se puede aplicar a las solicitudes de actualización. Para controlar los errores de solicitud de actualización, puede establecer un umbral en el código, como 10 errores consecutivos, y realizar un seguimiento del número de errores de las solicitudes a la región primaria. Una vez alcanzado el umbral, puede cambiar la aplicación al modo de solo lectura para que las solicitudes de actualización a la región primaria ya no se emita.

Cómo implementar el patrón de disyuntor

El control de errores que pueden tardar una cantidad variable de tiempo en recuperarse forma parte de un patrón de diseño arquitectónico denominado patrón disyuntor. La implementación adecuada de este patrón puede impedir que una aplicación intente ejecutar repetidamente una operación que probablemente produzca un error, lo que mejora la estabilidad y resistencia de la aplicación.

Un aspecto del patrón disyuntor es identificar cuándo hay un problema continuo con un punto de conexión principal. Para hacer esta determinación, puede supervisar la frecuencia con la que el cliente encuentra errores que se pueden reintentar. Como cada escenario es diferente, necesitará determinar el umbral adecuado que usar para la decisión de cambiar al punto de conexión secundario y ejecutar la aplicación en modo de solo lectura.

Por ejemplo, podría decidir cambiar si hay diez errores consecutivos en la región primaria. Para realizar un seguimiento de esto, mantenga un recuento de los errores en el código. Si se realiza correctamente antes de alcanzar el umbral, establezca el recuento en cero. Si el recuento alcanza el umbral, cambie la aplicación para que use la región secundaria para las solicitudes de lectura.

Como enfoque alternativo, podría decidir implementar un componente de supervisión personalizado en la aplicación. Este componente podría hacer ping continuamente al punto de conexión de almacenamiento principal con solicitudes de lectura triviales (como leer un blob pequeño) para determinar su estado. Este enfoque consumiría recursos, pero no en cantidades notables. Cuando se detecte un problema que alcance el umbral, cambiaria a las solicitudes de modo SecondaryOnly de lectura y al de solo lectura. Para este escenario, cuando vuelva a hacer ping correctamente al punto de conexión de almacenamiento principal, cambie la región primaria y continúe permitiendo las actualizaciones.

El umbral de errores que se usa para determinar cuándo se debe realizar el cambio puede variar para cada servicio en la aplicación, por lo que debería considerar convertir los umbrales en parámetros configurables.

Otra consideración es cómo controlar varias instancias de una aplicación y qué hacer cuando se detecten errores con posibilidad de reintento en cada instancia. Por ejemplo, puede que tenga 20 máquinas virtuales que se ejecutan con la misma aplicación cargada. ¿Va a controlar cada instancia por separado? Si una instancia empieza a tener problemas, ¿desea limitar la respuesta a solo esa instancia? O bien, ¿desea que todas las instancias respondan de la misma manera cuando una instancia tiene un problema? Controlar las instancias por separado es mucho más sencillo que intentar coordinar la respuesta en todas ellas, pero su enfoque depende de la arquitectura de la aplicación.

Control de datos con coherencia final

El almacenamiento con redundancia geográfica funciona mediante la replicación de transacciones de la región primaria en la región secundaria. El proceso de replicación garantiza que los datos de la región secundaria tengan coherencia final. Esto significa que todas las transacciones en la región primaria aparecerán eventualmente en la región secundaria, pero pueden tardar un tiempo en hacerlo. Tampoco hay ninguna garantía de que las transacciones lleguen a la región secundaria en el mismo orden en que se aplicaron originalmente en la región primaria. Si las transacciones llegan desordenadas a la región secundaria, podría considerar que los datos en la región secundaria se encuentran en un estado incoherente hasta que el servicio se ponga al día.

En el ejemplo siguiente de almacenamiento de Azure Table muestra un ejemplo de lo que podría suceder al actualizar los detalles de un empleado para convertirlo en miembro del rol de administrador. Para este ejemplo, esto requiere actualizar la entidad empleado y actualizar una entidad rol de administrador con un recuento del número total de administradores. Observe cómo se aplican las actualizaciones desordenadas en la región secundaria.

Time Transacción Replicación Hora de última sincronización Resultado
T0 Transacción A:
Insertar entidad
empleado en región primaria
Transacción A insertada en región primaria,
aún sin replicar.
T1 Transacción A
replicada en
región secundaria
T1 Transacción A replicada en región secundaria.
Hora de última sincronización actualizada.
T2 Transacción B:
Actualizar
entidad empleado
en región primaria
T1 Transacción B escrita en región primaria,
aún sin replicar.
T3 Transacción C:
Actualizar
administrator
entidad rol administrador en región
primary
T1 Transacción C escrita en región primaria,
aún sin replicar.
T4 Transacción C
replicada en
región secundaria
T1 Transacción C replicada en región secundaria.
LastSyncTime no actualizado porque
la transacción B está aún sin replicar.
T5 Leer entidades
desde región secundaria
T1 Obtiene el valor obsoleto de la entidad
empleado porque la transacción B aún
no se ha replicado. Obtiene el valor nuevo para la
entidad rol de administrador porque C
se ha replicado. La hora de la última sincronización aún no se ha
actualizado porque la transacción B
no se ha replicado. Puede ver que la
entidad rol de administrador es incoherente
porque la fecha y hora de la entidad son posteriores a
Hora de última sincronización.
T6 Transacción B
replicada en
región secundaria
T6 T6: todas las transacciones hasta la C
se han replicado, Hora de última sincronización
se ha actualizado.

En este ejemplo, suponga que el cliente pasa a leer desde la región secundaria en T5. Puede leer correctamente la entidad rol administrador en este momento, pero la entidad contiene un valor para el recuento de administradores que no es coherente con el número de entidades empleado que están marcadas como administradores en la región secundaria en este momento. El cliente podría mostrar este valor y correr el riesgo de que sea información incoherente. O bien, el cliente podría intentar determinar que el rol administrador se encuentra en un estado potencialmente incoherente porque las actualizaciones se han producido desordenadas y después informar al usuario sobre este hecho.

Para determinar si una cuenta de almacenamiento tiene datos potencialmente incoherentes, el cliente puede comprobar el valor de la propiedad Hora de la última sincronización. La Hora de última sincronización indica la hora en que los datos de la región secundaria fueron coherentes por última vez y cuándo aplicó el servicio todas las transacciones hasta ese momento. En el ejemplo mostrado antes, una vez que el servicio inserta la entidad empleado en la región secundaria, la hora de la última sincronización se establece en T1. Permanece en T1 hasta que el servicio actualiza la entidad empleado en la región secundaria cuando se establece en T6. Si el cliente recupera la última hora de sincronización cuando lee la entidad en T5, puede compararla con la marca de tiempo en la entidad. Si la marca de tiempo en la entidad es posterior a la hora de la última sincronización, la entidad se encuentra en un estado potencialmente incoherente, por lo que podrá llevar a cabo la acción adecuada. Para usar este campo, es necesario saber cuándo se completó la última actualización en la región primaria.

Para aprender a consultar la hora de la última sincronización, vea Comprobación de la propiedad Hora de la última sincronización de una cuenta de almacenamiento.

Prueba

Es importante comprobar que la aplicación se comporte según lo esperado cuando encuentre errores con posibilidad de reintento. Por ejemplo, debe probar que la aplicación cambie a la región secundaria cuando se detecte un problema y que entonces cambie de vuelta a la región primaria cuando vuelva a estar disponible. Para probar este comportamiento, necesita una manera de simular errores con posibilidad de reintento y controlar la frecuencia con que se producen.

Una opción es usar Fiddler para interceptar y modificar respuestas HTTP en un script. Este script puede identificar las respuestas que proceden de su punto de conexión principal y cambiar el código de estado HTTP para que la biblioteca del cliente de almacenamiento lo reconozca como error con posibilidad de reintento. Este fragmento de código muestra un ejemplo sencillo de un script de Fiddler que intercepta respuestas a solicitudes de lectura para la tabla employeedata y devuelve un estado 502:

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

Puede ampliar este ejemplo para interceptar una gama más amplia de solicitudes y solamente cambiar el elemento responseCode en algunas de ellas para simular mejor un escenario real. Para más información sobre cómo personalizar scripts de Fiddler, consulte Modifying a Request or Response (Modificación de una solicitud o respuesta) en la documentación de Fiddler.

Si ha hecho configurables los umbrales para cambiar la aplicación al modo de solo lectura, será más fácil probar el comportamiento con volúmenes de transacciones que no sean de producción.


Pasos siguientes

Para ver un ejemplo completo de cómo cambiar entre el punto de conexión principal y el secundario, consulte Ejemplos de Azure: uso del patrón de interruptor con almacenamiento de RA-GRS.