Transactions 执行顺序操作的最佳实践

Transactions 执行顺序操作的最佳实践,transactions,Transactions,同时执行两个任务的最佳方式是什么?如果一个任务失败,则不应完成下一个任务?我知道如果是数据库操作,那么我应该使用事务,但我谈论的是不同类型的操作,例如: 所有任务必须通过: 发送电子邮件 档案室数据库 创建文件 在上述场景中,所有任务都必须通过,否则整个批处理操作必须回滚 在C中# 返回sendmail()&&ArchiveResportsInDatabase()&&CreateAFile() 如果您的语言允许,这是非常整洁的: 将任务放入代码块或函数指针数组中 迭代数组 如果任何块返回失败,则

同时执行两个任务的最佳方式是什么?如果一个任务失败,则不应完成下一个任务?我知道如果是数据库操作,那么我应该使用事务,但我谈论的是不同类型的操作,例如:

所有任务必须通过:

发送电子邮件 档案室数据库 创建文件

在上述场景中,所有任务都必须通过,否则整个批处理操作必须回滚

在C中#


返回sendmail()&&ArchiveResportsInDatabase()&&CreateAFile()

如果您的语言允许,这是非常整洁的:

  • 将任务放入代码块或函数指针数组中
  • 迭代数组
  • 如果任何块返回失败,则中断
  • 另一个想法是:

    try {
        task1();
        task2();
        task3();
        ...
        taskN();
    }
    catch (TaskFailureException e) {
        dealWith(e);
    }
    

    您没有提到您正在使用的编程语言/环境。如果是.NET框架,您可能想看看。它描述了Microsoft Robotics Studio的并发和控制运行时,它允许您对一组(异步)事件应用各种规则:例如,您可以等待任意数量的事件完成,如果一个事件失败则取消,等等。它还可以在多个线程中运行,因此,您可以获得一种非常强大的方法来完成任务。

    您不需要指定您的环境。在unixshell脚本中,&&操作符就是这样做的

    SendEmail () {
      # ...
    }
    ArchiveReportsInDatabase () {
      # ...
    }
    CreateAFile () {
      # ...
    }
    
    SendEmail && ArchiveReportsInDatabase && CreateAFile
    

    有几点建议:

    在分布式场景中,可能需要某种两阶段提交协议。基本上,您向所有参与者发送一条消息,告诉他们“准备做X”。然后,每个参与者必须发送一个回复,说“好的,我保证我能做X”或“不,不能做”。如果所有参与者都保证他们能完成,然后发送消息告诉他们去做。“保证”可以根据需要严格

    另一种方法是为每个操作提供某种撤销机制,然后具有如下逻辑:

    try:
        SendEmail()
        try:
            ArchiveReportsInDatabase()
            try:
                 CreateAFile()
            except:
                UndoArchiveReportsInDatabase()
                raise
        except:
            UndoSendEmail()
            raise
    except:
        // handle failure
    

    (您不希望您的代码看起来像这样;这只是逻辑应该如何流动的一个示例。)

    如果您使用的是使用(Java和C#do)的语言,您可以简单地执行以下操作:

    return SendEmail() && ArchiveResportsInDatabase() && CreateAFile();
    

    如果所有函数都返回true,则返回true,并且在第一个函数返回false时立即停止。

    异常通常适用于此类情况。伪Java/JavaScript/C++代码:

    try {
        if (!SendEmail()) {
            throw "Could not send e-mail";
        }
    
        if (!ArchiveReportsInDatabase()) {
            throw "Could not archive reports in database";
        }
    
        if (!CreateAFile()) {
            throw "Could not create file";
        }
    
        ...
    
    } catch (Exception) {
        LogError(Exception);
        ...
    }
    
    如果您的方法本身抛出异常,则更好:

    try {
        SendEmail();
        ArchiveReportsInDatabase();
        CreateAFile();
        ...
    
    } catch (Exception) {
        LogError(Exception);
        ...
    }
    
    这种风格的一个非常好的结果是,当您沿着任务链向下移动时,代码不会越来越缩进;所有方法调用都保持在相同的缩进级别。缩进过多会使代码更难阅读


    此外,代码中只有一点用于错误处理、日志记录、回滚等。

    要真正做到这一点,您应该使用异步消息传递模式。我刚刚完成了一个项目,在这个项目中我使用了和MSMQ

    基本上,每一步都是通过向队列发送消息来完成的。当nServiceBus发现队列中等待的消息时,它将调用对应于该消息类型的句柄方法。这样,每个单独的步骤都可以独立地失败和恢复。如果一个步骤失败,消息将进入错误队列,以便您稍后可以轻松重试


    所建议的这些纯代码解决方案并没有那么健壮,因为如果一个步骤失败,您将无法在将来仅重试该步骤,并且您必须实现回滚代码,而在某些情况下甚至不可能实现回滚代码。

    回滚非常困难-好吧,实际上只有两种方法。要么是a,要么是。你真的必须找到一种方法,以这种方式来组织你的任务

    通常,更好的办法是利用其他人的辛勤工作,使用已经内置了2PC或补偿的技术。这就是RDBMS如此流行的原因之一

    因此,细节取决于任务……但模式相当简单:

    class Compensator {
       Action Action { get; set; }
       Action Compensate { get; set; }
    }
    
    Queue<Compensator> actions = new Queue<Compensator>(new Compensator[] { 
       new Compensator(SendEmail, UndoSendEmail),
       new Compensator(ArchiveReportsInDatabase, UndoArchiveReportsInDatabase),
       new Compensator(CreateAFile, UndoCreateAFile)
    });
    
    Queue<Compensator> doneActions = new Queue<Compensator>();
    while (var c = actions.Dequeue() != null) {
       try {
          c.Action();
          doneActions.Add(c);
       } catch {
          try {
            doneActions.Each(d => d.Compensate());
          } catch (EXception ex) {
            throw new OhCrapException("Couldn't rollback", doneActions, ex);
          }
          throw;
       }
    }
    
    类补偿器{
    动作动作{get;set;}
    动作补偿{get;set;}
    }
    队列操作=新队列(新补偿器[]{
    新补偿器(SendEmail、UndoSendEmail),
    新补偿器(ArchiveReportsInDatabase、UndoArchiveReportsInDatabase),
    新补偿器(CreateAFile、UndoCreateAFile)
    });
    Queue doneActions=new Queue();
    while(var c=actions.Dequeue()!=null){
    试一试{
    c、 动作();
    加入(c);
    }抓住{
    试一试{
    每个(d=>d.Compensate());
    }捕获(例外情况除外){
    抛出新异常(“无法回滚”,doneActions,ex);
    }
    投掷;
    }
    }
    
    当然,对于你的特定任务,你可能很幸运

    • 显然,RDBMS工作已经可以包装在事务中
    • 如果您使用的是Vista或Server2008,那么您就可以介绍CreateFile场景
    • 电子邮件有点棘手-我不知道有任何2PC或补偿(不过,如果有人指出Exchange有一个,我只会有点惊讶),所以我可能会写一个通知,让订阅者拿起它并最终发送电子邮件。此时,您的事务实际上只包括将消息发送到队列,但这可能已经足够好了

    所有这些都可以参与事务,因此您的状态应该很好。

    当一个流程失败时,它会停止该流程,并且在完成您想要的任务时,我可以看到的步骤/结构最短。更简单:return sendmail()&&ArchiveResportsInDatabase()&&CreateAFile();如果CreateAFile()失败,其他步骤仍将完成。这就是你想要的吗?大量的代码缩进使其更难阅读。如果你的操作不止3次,而是10次呢?如果CreateFile失败了,一切都会失败。使用.net C#windows应用程序我正在使用上述方法,但考虑是否还有其他更好的方法!但如果CreateAFile返回false,则SendEmail和ArchiverReportsInDatabase已经执行。我想