C# TxSelect和TransactionScope

C# TxSelect和TransactionScope,c#,transactions,rabbitmq,C#,Transactions,Rabbitmq,最近,我一直在检查C#作为实现发布/订阅的一种方式。我更习惯于使用。NServiceBus通过在TransactionScope中登记MSMQ来处理事务。其他事务感知操作也可以登记在相同的TransactionScope(如MSSQL)中,因此所有操作都是真正的原子操作。在下面,NSB引入MSDTC进行协调 我看到在RabbitMQ的C#client API中有一个IModel.TxSelect()和IModel.TxCommit()。这很好地解决了在提交之前不向exchange发送消息的问题。

最近,我一直在检查C#作为实现发布/订阅的一种方式。我更习惯于使用。NServiceBus通过在
TransactionScope
中登记MSMQ来处理事务。其他事务感知操作也可以登记在相同的
TransactionScope
(如MSSQL)中,因此所有操作都是真正的原子操作。在下面,NSB引入MSDTC进行协调


我看到在RabbitMQ的C#client API中有一个
IModel.TxSelect()
IModel.TxCommit()
。这很好地解决了在提交之前不向exchange发送消息的问题。这涵盖了将多条消息发送到需要原子化的exchange的用例。但是,有没有一种好方法可以将数据库调用(比如MSSQL)与RabbitMQ事务同步

据我所知,TxSelect/TxCommit无法与TransactionScope协调

目前,我采用的方法是使用持久队列和持久消息,以确保它们在RabbitMQ重新启动后仍然有效。然后,当从队列中消费时,我从队列中读取一条消息并进行一些处理,然后将一条记录插入数据库,完成所有这些操作后,我确认(确认)该消息并将其从队列中删除。这种方法的潜在问题是,消息可能会被处理两次(例如,如果消息被提交到DB,但在消息被确认之前断开与RabbitMQ的连接),但对于我们正在构建的系统,我们担心的是吞吐量。(我认为这被称为“至少一次”方法)

RabbitMQ站点确实指出,使用TxSelect和TxCommit会对性能产生重大影响,因此我建议对这两种方法进行基准测试

无论如何,您需要确保您的消费者能够处理可能被处理两次的消息



如果您还没有找到它,请参阅.Net RabbitMQ用户指南,特别是第3.5节,您可以通过实现该接口编写一个RabbitMQ资源管理器供MSDTC使用。该实现在登记参与时为事务管理器提供两阶段提交通知回调。请注意,MSDTC价格昂贵,将大幅降低您的整体性能

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();
    }
}
使用资源管理器的示例

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

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

假设您已经为抽象IServiceBus提供了一个服务总线实现。我们可以假装它是藏在引擎盖下的rabbitmq,但当然不需要

调用servicebus.Publish时,可以检查System.Transaction.Current以查看是否在事务中。如果您是,并且它是mssql服务器连接的事务,那么您可以发布到sql server中的代理队列,而不是发布到rabbit,该队列将尊重您正在执行的任何数据库操作的提交/回滚(您希望在此处执行一些连接魔术,以避免代理发布将txn升级到msdtc)


现在,您需要创建一个服务,该服务需要读取代理队列并向rabbit进行实际发布,这样,对于非常重要的事情,您可以看到您的数据库操作之前已经完成,并且消息将在将来的某个时间(当服务转发它时)发布到rabbit。如果在提交代理接收异常时发生异常,这里仍然可能出现故障,但问题的窗口会大大减少,更糟糕的情况是,如果您最终多次发布,您将永远不会丢失消息。这是非常不可能的,sql server在接收之后但在提交之前脱机将是一个示例,表明您将以最少的双重发布(当服务器联机时,您将再次发布)结束。您可以智能构建服务以减轻一些影响,但除非您使用msdtc及其所有附带功能(yikes)或构建自己的msdtc(哎呀哎呀)您可能会遇到潜在的故障,这一切都是为了使窗口变小,并且不太可能发生。

您希望您的系统有什么样的吞吐量?@kzhen我并不真正担心性能。但一致性很重要。我将使用持久的交换和队列。吞吐量不会那么高,可能是50-100000我每天的消息。是的,我同意你写的每一条消息。但是,在我的特殊情况下,消费者不是问题。我有一种情况,生产者可能会提交数据库状态,然后发布一条消息。不过,我再次完全同意消费者端需要幂等消息。感谢您对TxSelect a的提醒nd TxCommit性能。也许您的方法可以让生产者提交db行,然后发送消息,然后更新行,在服务器确认接收时说消息已发送(),然后如果生产者在恢复联机时崩溃,则可以查找尚未发布消息的行,然后(重新)发送消息是的,我认为你是对的。我们计划提交消息需要与原始db事务一起发送。然后让调度员将其拾取并发送给Rabbit。最后,我们将提交消息已发送。感谢链接!很有意思……但是,最后我们采用了无MSDTC解决方案。谢谢不过,将此添加到问题中,希望其他人会发现它很有用。:)我在应用程序中使用相同的方法。但是,我发现IEnlistmentNotification的提交/准备方法是在不同的线程中调用的(这似乎是System.Transaction中的正常行为),这会导致RabbitMQ出现一些问题,因为IModel不应跨线程使用(从官方文档)。你试验过这个问题吗?不,我没有。我认为它们指的是线程并发,因为通道不是线程安全的。因此,如果超过一个