通过调用.NET中的分布式系统进行批处理的建议

时间:2014-08-24 17:19:15

标签: c# .net batch-processing distributed-computing

我希望得到一些关于我们团队几乎每个项目似乎都会重现的问题的提示。

在这些项目中,主要目标通常是对大量的项目进行某种处理。 '处理'基本上是一系列动作,每个动作都可能由于各种原因而失败。

也许我可以通过描述一个示例应用程序来解释它。

想象一下以下作为我们的一个应用程序的简化版本:(实际上可能是1000 LoC)

foreach (var pdfFile in unprocessedPdfFiles)
{
    var mailWasSent = SendMail(pdfFile);

    if(!mailWasSent)
    {
        PrintFile(pdfFile);
    }

    MarkAsProcessed(pdfFile);
}

虽然这些是有问题的要求:

  • 每天都有数千个要处理的文件
  • 要处理文件,我们需要执行混合的数据库操作和对外部系统的调用(无法进行交易)
  • SendMail()可能因各种原因失败,即
    • 与邮件服务器的连接失败(稍后将自动重试该尝试,而不会阻止其他文件的处理)
    • 地址拼写错误(可能需要手动纠正并在之后重试)
    • 其他出乎意料的原因,没有人会预期,只有在应用程序运行良好后才会变得清晰
  • 正确发送的邮件可以反弹'回来 - 可能是发送后的几天。在收到有关反弹的通知后,需要打印出文件。
  • 文件的实际打印可能会失败而应用程序无法通知。 (即打印机故障)
  • 我们的老板可能会问以下问题:
    • 上周发送或打印了哪些文件?
    • 文件ABC预计会被打印出来,但它已经丢失了。应用程序是否尝试打印它?如果是,何时?
    • 文件XYZ发生了什么,我们多久以及何时尝试发送和打印它?

我认为这是我们最大的困难:

  1. 要记录应用程序的作用,我们需要有一个可搜索的历史记录:
    • 正确处理了哪些项目
    • 处理项目时发生的错误
  2. 我们要了解如何修复'失败的项目有效,没有副作用。
    • 在某些情况下,将失败的商品标记为“未经处理的”'是可行的。再次,所以它只是再次处理
    • 但在其他情况下,我们无法从开始重新处理该项目,因为先前失败的尝试可能已经导致无法回滚的副作用。 (在先前失败的步骤中恢复处理的方法可能是好的)
  3. 通常情况下,在“某事物”之后重复处理步骤才有意义。其他事情已经发生(也许我们需要修复一个错误,或者外部Web服务需要再次可用。)这意味着我们不能在任何地方使用重试循环,但需要记住错误。一种能够在以后检查并重试的方法。
    • 我们希望跟踪“修复尝试”的问题。以及有人试图修理项目时发生的事情
  4. 代码变得混乱,有很多非业务逻辑,主要是因为其他问题。 (异常处理,控制逻辑等)
  5. (注意:处理性能通常没有问题。)

    以下是我们过去尝试解决这些问题的方法:

    • 所有处理都在Windows服务的C#循环中完成
    • 要处理的项目由数据库表中的行表示(称为" Trigger" -rows)
    • 处理后,触发行标有状态标志,其中包含以下含义之一:"完成","打印错误","未知错误",等
    • 一些平面文件记录,用于最后的故障排除(NLog)
    • 在触发器表上使用SQL获取有关已处理项的信息
    • 将触发状态设置为"未处理"重复错误项目

    我确信有很多专家在这方面有很多长期经验。 (无论它叫什么) 但是我无法通过搜索网络找到很多实用的建议,所以我希望在stackoverflow上我可以得到一些建议。

    我在网上找到了有趣的框架,但到目前为止还犹豫不决:

    • ' BatchFlow'框架(也在NuGet上)
      • 我想这有助于保持我们的代码更清晰,但会让我们遇到所有其他问题,例如日志记录和异步错误恢复。
    • 消息框架,如MassTransit或EasyNetQ。 我可以看到消息传递如何帮助解决我们的一些问题,比如稍后可以重试工作流的单个步骤,但是:
      • 无论使用什么框架,似乎都没有一种简单的方法来检查和重试错误消息。 看起来每个消息传递框架基本上只是将错误消息抛出到错误队列,就是这样。 但是为了检查并重试这些错误,你似乎总是需要实现相当多的额外逻辑。 一个想法是使用所有错误消息并将它们放到数据库中,但我认为 为什么类似这样的东西已经成为框架的一部分? ......以及其他人如何处理他们的错误?
      • 我希望通过消息传递,可以很容易地保存关于处理的消息的历史记录 一个商业交易,但这似乎也是你必须在消息传递之上完全实现的东西 框架。 (或者我可能试图用错误的方法来解决问题。)

    希望帖子不会太混乱,但我很乐意在需要的地方详细说明。

1 个答案:

答案 0 :(得分:1)

首先,这是一个需要立即解决的大问题。

这是一个企业级问题,最好在更高级别的抽象上解决。在SOA术语中,您必须将系统分解为仅执行其需要执行操作的较小应用程序。想想SOLID [1]。思考单一责任。

将应用程序分解为更小的应用程序后,您可以使用像Mule [2]或Apache Camel [3]这样的集成中心来集成消息交换。

微服务架构[4]通过创建彼此隔离服务的边界来很好地解决问题。按业务领域或职能分组服务。

以下是一些可以让您的生活更轻松的提示:

  1. 使用AWS等托管云服务来减少应用程序的责任。例如,对于文件管理,请使用AWS S3 [5]。要发送电子邮件,请使用AWS SNS主题[6],它允许您可靠地发送电子邮件。或者,使用作为托管SMTP服务器的AWS SES。使用托管服务的优点是您不需要处理诸如记录或管理故障之类的低级工作。让PAAS为您处理。

  2. 消息队列的职责是为共享信息提供持久,可靠的通道。它用于以松散耦合的方式可靠地接收和发送消息给多个消费者和生产者。它也不会帮助您写入数据库。

  3. 如果您想在处理数据库时也写入数据库,请考虑将数据写入Apache Kafka [7]或AWS Kinesis Stream [8]等流。您可以创建多个收件人来收听流,并对数据进行操作。例如,一个客户端可以处理数据并将结果保存到数据库。另一个客户端监听器可以负责记录数据。

  4. 对所有API调用使用重试策略。延迟和超时等瞬态故障在分布式处理中非常常见。它们可以在一段时间后消失,并且可以使用重试来处理。有一个很好的重试策略框架用C#编写,称为Polly [9]。

  5. 如果在最大重试限制后无法解决的故障,请向死信队列发送消息[10]。 AWS Lamdba支持执行将失败的执行作为消息发送到死信队列的代码。 AWS Lambda将自动重试,因此您可以专注于编写代码以执行其所需的操作。任何执行失败都将转到DLQ。另一个Lambda函数可用于处理DLQ中的消息。

  6. 我希望您开始了解如何解决问题,并使用一些良好的原则,您将能够构建一个更强大,可扩展且更具弹性的系统。

    [1] https://www.codeproject.com/Articles/703634/SOLID-architecture-principles-using-simple-Csharp

    [2] https://developer.mulesoft.com/

    [3] http://camel.apache.org/

    [4] http://microservices.io/patterns/microservices.html

    [5] https://aws.amazon.com/s3/

    [6] https://aws.amazon.com/sns/

    [7] https://kafka.apache.org

    [8] https://aws.amazon.com/kinesis/streams/

    [9] https://github.com/App-vNext/Polly

    [10] http://docs.aws.amazon.com/lambda/latest/dg/dlq.html