Transacciones distribuidas para bases de datos heterogéneas en MS .NET

Recientemente, en una entrevista me preguntaron si trabajaba con transacciones distribuidas, en el sentido de que tenía que hacer la inserción / actualización de dichos registros, siempre que:

  1. Transacción individual
  2. Pueden ser varias bases de datos diferentes, como Oracle, MS SQL Server y PostgreSQL.
  3. La respuesta a una operación CRUD puede ser significativa.
  4. La secuencia de inserción no es importante.

¿Qué lograron los colegas al hacer esta pregunta? ¿Verifica mi experiencia u obtiene una solución preparada? En realidad, no es importante para mí, pero lo que es importante: el problema en la teoría de la pregunta me pareció interesante, y decidí escribir un artículo sobre cómo se podría resolver este problema.

Antes de describir mi solución, recordemos cómo se implementa una transacción distribuida típica utilizando la plataforma de Microsoft como ejemplo.

Opción número 1. Aplicación C ++ y controlador ODBC (propiedad del Coordinador de transacciones distribuidas de Microsoft (MSDTC) para SQL Server)

El Coordinador de transacciones distribuidas de Microsoft (MSDTC) permite que las aplicaciones expandan o distribuyan transacciones en dos o más instancias de SQL Server. Una transacción distribuida funciona incluso si dos instancias se encuentran en computadoras diferentes.

MSDTC solo funciona localmente para Microsoft SQL Server y no está disponible para el servicio de base de datos en la nube Microsoft Azure SQL.

MSDTC es llamado por el controlador de SQL Server Native Client para Open Database Connectivity (ODBC) cuando su programa C ++ administra una transacción distribuida. El controlador ODBC del cliente nativo tiene un administrador de transacciones compatible con el procesamiento de transacciones distribuidas abiertas XA (DTP). Este cumplimiento es requerido por MSDTC. Por lo general, todos los comandos de administración de transacciones se envían a través de este controlador ODBC para el cliente nativo. La secuencia es la siguiente:

una aplicación ODBC para un cliente nativo de C ++ inicia una transacción invocando SQLSetConnectAttr con la confirmación automática desactivada.
La aplicación actualiza algunos datos en SQL Server X en la computadora A.
La aplicación actualiza algunos datos en SQL Server X en la computadora B.
Si la actualización falla en SQL Server Y, todas las actualizaciones no confirmadas en ambas instancias de SQL Server se revierten.
Finalmente, la aplicación completa la transacción llamando a SQLEndTran (1) con la opción SQL_COMMIT o SQL_ROLLBACK.

(1) Se puede llamar a MSDTC sin ODBC. En este caso, MSDTC se convierte en un administrador de transacciones y la aplicación ya no usa SQLEndTran.

Solo una transacción distribuida.

Suponga que su aplicación ODBC para un cliente nativo de C ++ está inscrita en una transacción distribuida. Luego, la aplicación se acredita a la segunda transacción distribuida. En este caso, el controlador ODBC de SQL Server Native Client deja la transacción distribuida original y se incluye en la nueva transacción distribuida.

Lea más sobre MSDTC aquí..

Opción número 2. La aplicación C #, como alternativa a la base de datos SQL en la nube de Azur,

MSDTC no es compatible con Azure SQL Database ni con Azure SQL Data Warehouse.

Sin embargo, puede crear una transacción distribuida para una base de datos SQL si su programa C # usa la clase .NET System.Transactions.TransactionScope.

El siguiente ejemplo muestra cómo usar la clase TransactionScope para definir un bloque de código para participar en una transacción.

static public int CreateTransactionScope(
    string connectString1, string connectString2,
    string commandText1, string commandText2)
{
    //        StringWriter   
   // .
    int returnValue = 0;
    System.IO.StringWriter writer = new System.IO.StringWriter();

    try
    {
        //  TransactionScope   , 
        //            
        //  .
        using (TransactionScope scope = new TransactionScope())
        {
            using (SqlConnection connection1 = new SqlConnection(connectString1))
            {
                //      
                // TransactionScope   .
                connection1.Open();

                //   SqlCommand    .
                SqlCommand command1 = new SqlCommand(commandText1, connection1);
                returnValue = command1.ExecuteNonQuery();
                writer.WriteLine("   command1: {0}", returnValue);

                //      ,  ,  
                //  command1  .  
                //    using  connection2   connection1, 
                //         connection2 
                //      .   
                using (SqlConnection connection2 = new SqlConnection(connectString2))
                {
                    //       
                    //   connection2 .
                    connection2.Open();

                    //      .
                    returnValue = 0;
                    SqlCommand command2 = new SqlCommand(commandText2, connection2);
                    returnValue = command2.ExecuteNonQuery();
                    writer.WriteLine("    command2: {0}", returnValue);
                }
            } 
            scope.Complete();
        }
    }
    catch (TransactionAbortedException ex)
    {
        writer.WriteLine("   : {0}", ex.Message);
    }

    Console.WriteLine(writer.ToString());

    return returnValue;
}

Opción número 3. C # y Entity Framework Core aplicaciones para transacciones distribuidas.

De manera predeterminada, si el proveedor de la base de datos admite transacciones, todos los cambios en una llamada a SaveChanges () se aplican a la transacción. Si alguno de los cambios no se realiza, la transacción se revierte y ninguno de los cambios se aplica a la base de datos. Esto significa que se garantiza que SaveChanges () se completará correctamente o dejará la base de datos sin cambios en caso de error.

Para la mayoría de las aplicaciones, este comportamiento predeterminado es suficiente. Debe controlar manualmente las transacciones solo si los requisitos de su aplicación lo consideran necesario.

Puede usar la API DbContext.Databasepara iniciar, confirmar y revertir transacciones. El siguiente ejemplo muestra dos operaciones SaveChanges () y una consulta LINQ ejecutada en una sola transacción.

No todos los proveedores de bases de datos admiten transacciones. Algunos proveedores pueden o no emitir transacciones al invocar una API de transacción.

using (var context = new BloggingContext())
{
    using (var transaction = context.Database.BeginTransaction())
    {
        try
        {
            context.Blogs.Add(new Blog { Url = "http://blogs.msdn.com/dotnet" });
            context.SaveChanges();

            context.Blogs.Add(new Blog { Url = "http://blogs.msdn.com/visualstudio" });
            context.SaveChanges();

            var blogs = context.Blogs
                .OrderBy(b => b.Url)
                .ToList();

           transaction.Commit();
        }
        catch (Exception)
        {
             
        }
    }
}

Si usa EF Core en .NET Framework , la implementación de System.Transactions allí admite transacciones distribuidas.

Como puede ver en la documentación dentro del marco de la plataforma y la línea de productos de Microsoft, las transacciones distribuidas no son un problema: MSDTC, la nube en sí es tolerante a fallas, un montón de ADO .NET + EF Core para combinaciones exóticas (lea aquí ).

¿Cómo puede ayudarnos la revisión de transacciones en la plataforma de Microsoft a resolver el problema? Muy simple, hay una clara comprensión de que simplemente no hay un kit de herramientas estándar para implementar una transacción distribuida a través de bases de datos heterogéneas en una aplicación .NET.

Y si es así, entonces hacemos nuestro propio coordinador de transacciones distribuidas coordinadas (CRT).

Una de las opciones de implementación son las transacciones distribuidas basadas en microservicios (ver aquí ). Esta opción no es mala, pero requiere un refinamiento serio de la API web para todos los sistemas involucrados en la transacción.

Por lo tanto, necesitamos un mecanismo ligeramente diferente. El siguiente es un enfoque general para desarrollar MCT.



Como puede ver en el diagrama, la idea principal es crear y almacenar con el estado actualizado el registro de transacciones almacenado en la base de datos maestra (o tabla). Este modelo de CRT le permite implementar transacciones distribuidas que cumplan los requisitos de:

  • atomicidad
  • coherencia
  • aislamiento
  • longevidad

: puede ser parte de la aplicación en la que el usuario ha creado un registro, o puede ser una aplicación completamente separada en forma de un servicio del sistema. puede contener un subproceso especial que genera transacciones en un grupo para su ejecución cuando se produce un corte de energía (no se muestra en el diagrama). Las reglas de negocio para convertir (mapear) los datos de origen generados por el usuario para las bases de datos relacionadas pueden agregarse y configurarse dinámicamente a través del formato XML / JSON y almacenarse en la carpeta de la aplicación local o en un registro de transacción (como opción). En consecuencia, para este caso, es aconsejable unificar la conversión a bases de datos relacionadas a nivel de código mediante la implementación de la modularidad de los convertidores en forma de archivos DLL. (Y sí, SRT implica acceso directo a la base de datos sin la participación de la API web).

Por lo tanto, SRT en una forma simple se puede implementar con éxito como parte de la aplicación, o como una solución separada, personalizable e independiente (que es mejor).

Una vez más, aclararé que una transacción distribuida es, por definición, un almacenamiento único de información sin cambiarla, pero con la capacidad de mapear varias bases de datos en esquemas de datos. Entonces, si necesita registrar datos en otras bases, por ejemplo, en el Ministerio del Interior, el FSB y las compañías de seguros, al registrar incidentes en la sala de emergencias del hospital (herida de bala), este enfoque ciertamente lo ayudará. El mismo mecanismo puede funcionar perfectamente en las instituciones financieras.

Espero que este enfoque les haya parecido interesante, ¡escriban lo que piensan al respecto, colegas y amigos!

All Articles