TxSelect y TransactionScope

Recientemente, he estado revisando RabbitMQ sobre C# como una forma de implementar pub/sub. Estoy más acostumbrado a trabajar con NServiceBus. NServiceBus maneja transacciones al inscribir a MSMQ en un TransactionScope. Otras operaciones conscientes de transacciones también pueden inscribirse en el mismo TransactionScope (como MSSQL) por lo que todo es verdaderamente atómico. Debajo, NSB trae MSDTC para coordinar.

Veo que en la API del cliente C# para RabbitMQ hay un IModel.TxSelect() y IModel.TxCommit(). Esto funciona bien para no enviar mensajes al intercambio antes de la confirmación. Esto cubre el caso de uso en el que se envían múltiples mensajes al intercambio que deben ser atómicos. Sin embargo, ¿existe una buena manera de sincronizar una llamada a la base de datos (por ejemplo, a MSSQL) con la transacción RabbitMQ?

preguntado el 31 de julio de 12 a las 12:07

¿Qué tipo de rendimiento espera de su sistema? -

@kzhen No estoy realmente preocupado por el rendimiento. Sin embargo, la consistencia es importante. Usaré intercambios duraderos y colas. El rendimiento no será tan alto, tal vez de 50 a 100,000 XNUMX mensajes por día. -

3 Respuestas

Puede escribir un administrador de recursos de RabbitMQ para que lo use MSDTC implementando el IEnlistmentNotification interfaz. La implementación proporciona devoluciones de llamada de notificación de confirmación de dos fases para el administrador de transacciones al inscribirse para participar. Tenga en cuenta que MSDTC tiene un alto precio y degradará drásticamente su rendimiento general.

Ejemplo de administrador de recursos RabbitMQ:

sealed class RabbitMqResourceManager : IEnlistmentNotification
{
    private readonly IModel _channel;

    public RabbitMqResourceManager(IModel channel, Transaction transaction)
    {
        _channel = channel;
        _channel.TxSelect();
        transaction.EnlistVolatile(this, EnlistmentOptions.None);
    }

    public RabbitMqResourceManager(IModel channel)
    {
        _channel = channel;
        _channel.TxSelect();
        if (Transaction.Current != null)
            Transaction.Current.EnlistVolatile(this, EnlistmentOptions.None);
    }

    public void Commit(Enlistment enlistment)
    {
        _channel.TxCommit();
        enlistment.Done();
    }

    public void InDoubt(Enlistment enlistment)
    {           
        Rollback(enlistment);
    }

    public void Prepare(PreparingEnlistment preparingEnlistment)
    {
        preparingEnlistment.Prepared();
    }

    public void Rollback(Enlistment enlistment)
    {
        _channel.TxRollback();
        enlistment.Done();
    }
}

Ejemplo usando el administrador de recursos

using(TransactionScope trx= new TransactionScope())
{
    var basicProperties = _channel.CreateBasicProperties();
    basicProperties.DeliveryMode = 2;

    new RabbitMqResourceManager(_channel, trx);
    _channel.BasicPublish(someExchange, someQueueName, basicProperties, someData);
    trx.Complete();
}

Respondido 30 Oct 13, 15:10

interesante... Pero, al final, optamos por una solución sin MSDTC. Sin embargo, gracias por agregar esto a la pregunta, espero que otros lo encuentren útil. :) - Davin Tryon

Yo uso el mismo enfoque en mi aplicación. Sin embargo, descubrí que los métodos Commit/Prepare de IEnlistmentNotification se llaman en un subproceso diferente (parece ser un comportamiento normal en System.Transaction), que causan algunos problemas con RabbitMQ porque IModel no debe usarse entre subprocesos (de la documentación oficial ). ¿Experimentaste este problema? - Normand Bedard

No, no lo hice. Creo que se refieren a la concurrencia de subprocesos ya que el canal no es seguro para subprocesos. Por lo tanto, si más de un subproceso utiliza el canal al mismo tiempo, tendrá problemas. - usuario2930590

Que yo sepa, no hay forma de coordinar TxSelect/TxCommit con TransactionScope.

Actualmente, el enfoque que estoy tomando es usar colas duraderas con mensajes persistentes para garantizar que sobrevivan a los reinicios de RabbitMQ. Luego, cuando consumo de las colas, leo un mensaje, realizo un procesamiento y luego inserto un registro en la base de datos, una vez que todo esto está hecho, ACK (reconozco) el mensaje y se elimina de la cola. El problema potencial con este enfoque es que el mensaje podría terminar procesándose dos veces (si, por ejemplo, el mensaje se envía a la base de datos pero dice que la conexión a RabbitMQ se desconecta antes de que se pueda confirmar el mensaje), pero para el sistema que estamos construyendo estamos preocupados por el rendimiento. (Creo que esto se llama el enfoque "al menos una vez").

El sitio de RabbitMQ dice que hay un impacto significativo en el rendimiento al usar TxSelect y TxCommit, por lo que recomendaría comparar ambos enfoques.

Independientemente de la forma en que lo haga, deberá asegurarse de que su consumidor pueda hacer frente al mensaje que posiblemente se procese dos veces.


Si aún no lo ha encontrado, eche un vistazo a la guía de usuario de .Net para RabbitMQ aquí, en concreto el apartado 3.5

Respondido 04 ago 12, 19:08

Sí, estoy de acuerdo con todo lo que escribiste. En mi caso particular, sin embargo, el consumidor no es el problema. Tengo una situación en la que el productor podría confirmar el estado de la base de datos y luego publicar un mensaje. Una vez más, sin embargo, estoy totalmente de acuerdo con la necesidad de mensajes idempotentes del lado del consumidor. Gracias por los avisos sobre el rendimiento de TxSelect y TxCommit. - Davin Tryon

Tal vez su enfoque podría hacer que el productor confirme la fila db, luego envíe un mensaje y luego actualice la fila para decir que el mensaje se envió cuando el servidor confirma la recepción (rabbitmq.com/blog/2011/02/10/introducing-publisher-confirma) entonces, si su productor falla cuando vuelve a estar en línea, podría buscar filas cuyos mensajes no se hayan publicado y luego (re)enviarlos: kzhen

Sí, creo que tienes razón. Vamos a planear confirmar que el mensaje debe enviarse con la transacción de base de datos original. Luego haga que un despachador lo recoja y se lo envíe a Rabbit. Finalmente, confirmaremos que el mensaje ha sido enviado. Gracias por el enlace! - Davin Tryon

Digamos que tiene una implementación de bus de servicio para su abstracción IServiceBus. Podemos pretender que es rabbitmq debajo del capó, pero ciertamente no es necesario.

Cuando llama a servicebus.Publish, puede verificar System.Transaction.Current para ver si está en una transacción. Si es una transacción para una conexión de servidor mssql, en lugar de publicar en conejo, puede publicar en una cola de intermediario dentro del servidor sql que respetará la confirmación/reversión con cualquier operación de base de datos que esté realizando (quiere hacer alguna conexión magia aquí para evitar que el corredor publique la actualización de su txn a msdtc)

Ahora necesita crear un servicio que necesite leer la cola del intermediario y hacer una publicación real en Rabbit, de esta manera, para cosas muy importantes, puede garantizar que la operación de su base de datos se completó previamente y que el mensaje se publica en Rabbit en algún momento. en el futuro (cuando el servicio lo transmita). Todavía es posible que se produzcan fallas aquí si al confirmar el intermediario recibe una excepción, pero la ventana de problemas se reduce drásticamente y, en el peor de los casos, terminaría publicando varias veces, nunca perdería un mensaje. Esto es muy poco probable, el servidor sql que se desconecta después de recibir pero antes de confirmar sería un ejemplo de cuándo terminaría con una publicación doble mínima (cuando el servidor se conecta, publicaría nuevamente) Puede construir su servicio de manera inteligente para mitigar algunos, pero a menos que use msdtc y todo lo que viene con él (yikes) o cree su propio msdtc (yikes yikes), tendrá fallas potenciales, se trata de hacer que la ventana sea pequeña y poco probable que ocurra.

contestado el 28 de mayo de 15 a las 19:05

No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas or haz tu propia pregunta.