c# - 有关在.NET中调用分布式系统的批处理建议

标签 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文件发生了什么,我们尝试发送和打印它的频率和时间是多少?



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


为了保持应用程序“做什么”的记录,我们需要具有以下内容的可搜索历史记录:


哪些项目已正确处理
处理项目时发生了什么错误

我们发现如何有效地“修复”失败的项目而没有副作用。


在某些情况下,将失败的项目再次标记为“未处理”是可行的,因此只需再次对其进行处理
但是在其他情况下,我们不能只是从头开始重新处理项目,因为先前的失败尝试可能已经引起了无法回滚的副作用。 (一种在先前失败的步骤中恢复处理的方法可能不错)

通常,只有在发生“其他情况”之后重复处理步骤才有意义(也许我们需要修复错误,或者需要再次使用外部Web服务。)这意味着我们不能只使用重试循环到处都是,但需要以某种方式记住该错误,以便以后可以检查并重试。


我们想跟踪“修复尝试”以及有人尝试修复项目后发生的情况

该代码被许多非业务逻辑所困扰,主要是由于其他问题。 (异常处理,控制逻辑等)


(注意:处理性能通常没有问题。)

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


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


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

我在网上找到了有趣的框架,但至今仍不愿使用:


“ BatchFlow”框架(也在NuGet上)


我想这可能有助于使我们的代码保持整洁,但会给我们带来其他所有问题,例如日志记录和异步错误恢复。

消息传递框架,例如MassTransit或EasyNetQ。
我可以看到消息传递如何帮助解决我们的一些问题,例如以后可以重试工作流的单个步骤,但是:


无论使用哪种框架,似乎从来都不存在检查和重试错误消息的简便方法。
看起来每个消息传递框架基本上只是将错误消息抛出到错误队列中,仅此而已。
但是,为了检查并重试这些错误,您似乎总是必须实现很多附加逻辑。
一种想法是使用所有错误消息,并为此目的将它们放入数据库中,但是我想
为什么这样的东西已经不是框架的一部分了? ...以及其他人如何处理错误?
我希望通过消息传递,可以很容易地保存有关处理了哪些消息的历史日志
商业交易,但这似乎也是您必须在消息传递之上完全实现的事情
框架。 (或者我正在尝试使用错误的方法来解决问题。)



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

最佳答案

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

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

将应用程序分解为较小的应用程序后,可以使用诸如Mule [2]或Apache Camel [3]之类的集成中心来集成消息交换。

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

这里有一些技巧,可以使您的生活更轻松:


使用AWS之类的托管云服务来减少应用程序的责任。例如,对于文件管理,请使用AWS S3 [5]。要发送电子邮件,请使用AWS SNS主题[6],它使您能够可靠地发送电子邮件。或者,使用作为托管SMTP服务器的AWS SES。使用托管服务的优点是您将不需要处理诸如记录或管理故障之类的底层工作。让PAAS为您处理。
消息队列的责任是为共享信息提供持久,可靠的渠道。它用于以松散耦合的方式可靠地接收消息并将消息发送给多个消费者和生产者。它也不会帮助您写入数据库。
如果要在处理数据库时也将其写入数据库,请考虑将数据写入Apache Kafka [7]或AWS Kinesis Stream [8]之类的流中。您可以创建多个收件人来侦听流,并对数据进行操作。例如,一个客户可以处理数据并将结果保存到数据库中。另一个客户端侦听器可以负责记录数据。
对所有API调用使用重试策略。诸如延迟和超时之类的瞬态故障在分布式处理中非常常见。他们可以在一段时间后消失,并可以通过重试来处理。有一个很好的用C#编写的重试策略框架,称为Polly [9]。
如果出现最大重试次数限制后仍无法解决的故障,请将消息发送至死信队列[10]。 AWS Lamdba支持执行代码,该代码将失败的执行作为消息发送到死信队列。 AWS Lambda将自动重试,因此您可以专注于编写代码以完成所需的工作。任何失败的执行将转到DLQ。另一个Lambda函数可用于处理DLQ中的消息。


我希望您开始了解如何解决该问题,并使用一些好的原则,您将能够构建一个更加健壮,可扩展和有弹性的系统。

[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

关于c# - 有关在.NET中调用分布式系统的批处理建议,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/25474277/

相关文章:

windows-mobile - 如何启动 Windows CE 设备中最小化的批处理文件(使用另一个批处理或快捷方式)?

c# - Microsoft.IdentityModel 与 System.IdentityModel

c# - 为什么我的 Storyboard 只运行一次?

c# - 无法对调用静态方法的方法进行单元测试,我该如何重新设计这个程序?

c# - CollectionView.View.Refresh 上的内存泄漏

java - 如何使用hibernate滚动ScrollableResults进行批处理?

C# - 实例化一个对象 A,其构造函数接受一个需要引用 A 的对象 B 的参数(如一对一关系)

c# - 使用缓冲流与在源流上使用读取方法读取相同数量的字节有何不同?

c# - Type.GetProperties 方法

java - Spring Batch 在 Spring Boot 中完成作业后发送响应