3

我有一个作业处理器,它需要并行处理约 300 个作业(作业最多可能需要 5 分钟才能完成,但它们通常是网络绑定的)。

我遇到的问题是,工作往往以特定类型的形式出现。为简单起见,假设有六种工作类型,JobA通过JobF.

JobA-JobE是网络绑定的,可以很高兴地让 300 个一起运行而不会对系统造成任何负担(实际上,我已经设法在测试中让超过 1,500 个并排运行)。JobF(一种新的作业类型)也是网络绑定的,但它需要大量内存并且实际上使用 GDI 功能。

我确保我仔细处理所有带有usings 的 GDI 对象,并且根据分析器,我没有泄漏任何东西。只是JobF并行运行 300 比 .NET 愿意给我的内存更多。

处理这个问题的最佳实践方法是什么?我的第一个想法是确定我有多少内存开销,并在我接近极限(至少是JobF作业)时限制生成新作业。我无法实现这一点,因为我找不到任何方法来可靠地确定框架愿意在内存方面分配给我什么。我还必须猜测一个看起来有点古怪的工作使用的最大内存。

我的下一个计划是在我收到 OOM 时简单地限制并重新安排失败的作业。不幸的是,OOM​​ 可能发生在任何地方,而不仅仅是在有问题的作业内部。事实上,最常见的地方是管理作业的主工作线程。就目前情况而言,这会导致进程正常关闭(如果可能)、重新启动并尝试恢复。虽然这很有效,但它既讨厌又浪费时间和资源——比仅仅回收那个特定的工作要糟糕得多。

是否有处理这种情况的标准方法(添加更多内存是一种选择并且会完成,但应用程序应该正确处理这种情况,而不仅仅是炸弹)?

4

4 回答 4

2

只是并行运行 300 个 JobF 使用的内存比 .Net 愿意给我的要多。

那么,不要这样做。在系统ThreadPool中排队您的作业。或者,或者,横向扩展并将负载分配到更多系统。

此外,如果发生内存不足异常,请查看CER以至少运行清理代码。

更新:另一件要注意的事情,因为你提到你使用 GDI,它可以抛出一个不是内存不足OutOfMemoryException东西。

于 2012-07-04T19:14:32.320 回答
2

我正在做一些与您的情况类似的事情,并且我选择了一种方法,其中我有一个任务处理器(在一个节点上运行的主队列管理器)和在一个或多个节点上运行的尽可能多的代理。

每个代理都作为一个单独的进程运行。他们:

  • 检查任务可用性
  • 下载所需数据
  • 处理数据
  • 上传结果

队列管理器的设计方式是,如果任何代理在作业执行期间失败,它将在一段时间后简单地重新分配给另一个代理。

8 个代理在一个盒子中并排运行

顺便说一句,考虑不要让所有任务同时并行运行,因为在切换上下文时确实存在一些开销(可能很大)。在您的情况下,您可能会使用不必要的 PROTOCOL 流量而不是真实的 DATA 流量使网络饱和。

这种设计的另一个优点是,如果我开始在数据处理方面落后,我总是可以再打开一台机器(比如 Amazon C2 实例)并运行多个代理,这将有助于更快地完成任务库。

回答你的问题:

每个主机都将尽可能多地占用,因为在一台主机上运行的代理数量是有限的。当一项任务完成时,另一项任务将被无限期执行。我不使用数据库。任务不是时间关键的,所以我有一个进程可以在传入的数据集上循环运行,并在之前的运行失败时创建新任务。具体来说:

http://access3.streamsink.com/archive/(源数据)

http://access3.streamsink.com/tbstrips/(计算结果)

在每次运行队列管理器时,都会扫描源和目标,减去结果集并将文件名转换为任务。

还有更多:

我正在使用 Web 服务来获取工作信息/返回结果,并使用简单的 http 来获取数据进行处理。

最后:

这是我拥有的 2 个经理/代理对中的一个更简单 - 另一个更复杂,所以我不会在这里详细介绍。使用电子邮件:)

于 2012-07-05T00:53:08.547 回答
1

理想情况下可以划分为进程配置文件。CPU 绑定,内存绑定,IO 绑定,网络绑定。我是并行处理方面的新手,但 TPL 擅长的是 CPU 限制,并且无法真正调整超过 MaxDegreeOfParallelism。

开始是 CPU 绑定获取 MaxDegreeOfParallelism = System.Environment.ProcessorCount -1

其他一切都得到 MaxDegreeOfParallelism = 100。我知道你说过网络的东西会扩大,但在某些时候限制是你的带宽。增加 300 个作业(占用内存)真的可以提高吞吐量吗?如果是这样,请查看 Joradao 的答案。

于 2012-07-04T23:09:32.283 回答
0

如果您的对象实现 IDisposable 接口,则不应依赖垃圾回收器,因为这可能会产生内存泄漏。

例如,如果您有该课程:

class Mamerto : IDisposable
{
    public void methodA()
    {
        // do something
    }

    public void methodB()
    {
        // do something
    }

    public void Dispose()
    {
        // release resources
    }

你以这种方式使用那个类:

using( var m = new Mamerto() )
{
    m.methodA();
    m.methodB();
    // you should call dispose here!
}

垃圾回收器会将 m 对象标记为“准备删除”,将其放入 Gen 0 回收中。当垃圾回收器尝试删除 Gen 0 上的所有对象时,检测到 Dispose 方法并自动将对象提升到 Gen 1(因为删除该对象并不“那么容易”)。Gen 1 对象的检查频率不如 Gen 0 对象,因此可能导致内存泄漏。

请阅读该文章以获取更多信息http://msdn.microsoft.com/en-us/magazine/bb985011.aspx

如果您继续进行明确的 Dispose,那么您可以避免这种烦人的泄漏。

using( var m = new Mamerto() )
{
    m.methodA();
    m.methodB();
    m.Dispose();
}
于 2012-07-04T16:55:19.700 回答