首页 > 解决方案 > iis工作进程内存使用和处理对象之间的关系?

问题描述

我们通过以下代码通过 Entity Framework 从 Sql Server 获取大量数据

    using (var db=new Entities())
    {
        var list = db.spGetRecs().ToList();

       ///rest of the codes
    }

记录数约为 400 万,之后 iis 变得如此繁重,它的内存使用量达到了大约690 mb,并且工作进程根本不释放内存。您可以简单地想到多个用户使用这么多内存的情况,然后当然out of memory exception会发生。

我要在这里提出三个问题:

1.首先,为什么iis工作进程不释放内存?

2.其次,我们如何强制iis工作进程在处理完数据后释放内存?

iis3.第三,为什么我把与这个庞大数据相关的所有对象都处理掉了,却对内存使用没有任何影响?!那么当它与windows进程无关时,处理对象有什么意义呢?!

我没有编写整行代码,因为我不想使问题复杂化并分散您对这个问题背后可能具有挑战性的概念的注意力。

顺便说一句,在我调用垃圾收集器 GC.Collect() 之后,它从 iis 工作进程中释放了大约 20Mb。

标签: c#asp.netentity-frameworkiis

解决方案


即使导致该内存分配的对象已被释放,进程仍然可以报告 RAM 的高分配。内存可能仍被视为保留但未提交,但老实说,通过释放死对象深入了解垃圾收集的工作原理并不是我真正需要担心的事情,除非在追踪无法解释的不断增长的内存使用时。

第一步应该始终最小化您的内存占用以适应并发请求。对于大型请求,您应该考虑实现一个带有后台进程的请求队列,以确保以在任何给定时间处理有限数量的并发请求的方式处理这些请求,或者使用不同服务器上的资源,以免影响网络服务器的响应能力。

最小化内存占用大小提示:

  1. 将包含轻量级实体定义的有界上下文用于您的流程所需的最少字段。例如,如果一个实体通常包含 50 多个列,其中一些字段是您不需要的大字符串、二进制数据等,则具有一个实体定义的有界上下文仅引用您需要的 10 个列将节省记忆。
  2. 对数据进行合理的分析。利用分页在任何时候只检索可管理的数据子集。即一次1000条记录。Skip然后利用子句查看返回TakeOrderBy记录数,以评估是否还有更多页面要检索。(而不是依赖潜在的昂贵Count查询。)

后台进程是繁重查询/处理的更好解决方案。如果它们可以针对只读副本而不是主数据库运行,那就更好了。您的 Web 服务器可以接收具有给定参数集的请求,而不是启动会使服务器饿死的昂贵查询,它们可以简单地在处理 Queue 表中创建一条记录,以向后台进程发出信号以获取该记录并处理它。该后台进程可以在完全不同的服务器或服务器场上运行。如果用户正在等待结果,Web 服务器可以定期轮询后台工作人员的状态更新,并在处理完成后显示处理 Queue 表或相关结果表中的结果。

编辑:当涉及到垃圾收集时,您会担心,因为进程(垃圾收集器)正在保持内存提交。您期望如果有问题的代码需要分配 1GB 的内存,那么在 dispose 甚至 GC.Collect() 之后,内存使用量将下降 1GB。它没有。内存仍将显示为已提交给进程,但仍可用于该进程的代码。您可以通过运行一个巨大的查询并让生成的实体/数据过期来测试这一点。例如,我有一个来自另一个 SO 问题的最新测试数据集,我在其中填充了 3M 行数据。在我的系统处于相当负载的情况下,我尝试使用以下方法将所有 3M 行读入内存:

using (var context = new TestDbContext())
{
    var test = context.Messages.ToList();
    Assert.IsTrue(true);
}

根据进程监视器,这搅动了一段时间,然后出现了大约 1.7GB 的内存不足异常。所以我把它改成:

using (var context = new TestDbContext())
{
    var test = context.Messages.Take(1000000).ToList();
    Assert.IsTrue(true);
}

这使用 ~500MB 的 RAM 完成。所以我让它连续执行 4 次:

using (var context = new TestDbContext())
{
    var test = context.Messages.Take(1000000).ToList();
    Assert.IsTrue(true);
}
using (var context = new TestDbContext())
{
    var test = context.Messages.Take(1000000).ToList();
    Assert.IsTrue(true);
}
using (var context = new TestDbContext())
{
    var test = context.Messages.Take(1000000).ToList();
    Assert.IsTrue(true);
}
using (var context = new TestDbContext())
{
    var test = context.Messages.Take(1000000).ToList();
    Assert.IsTrue(true);
}

现在,如果最初的 1M “泄漏”,那么我的应用程序进程将耗尽内存。除非它没有。第一个攀升至~500MB,第二个从那里攀升至~1.1GB 第三个达到1.6GB。下一个开始爬升,然后锯齿状下降到 ~1GB 并继续爬回 ~1.5GB。随后的每次读取都被锯齿化,以保持在 1.4 到 1.5GB 之间。我重复了 6、10 次阅读。有时它锯齿状地低至 200MB,有时几乎没有。没有内存不足异常或交换到磁盘的性能下降。调用 GC.Collect() 并没有明显地释放内存,即使重复调用或在每个using块之后调用也是如此。内存被“释放”并在每个后续调用中重复使用。每次运行或附加读取块的性能是一致的。

当并行运行多个测试实例时,您可能会开始看到性能下降,因为可以看到每次运行都分配了尽可能多的内存,直到达到限制。所需的组合 RAM 超过了系统上的可用内存,因此每个 RAM 轮流将其内存块缓存到磁盘。结果是性能显着下降,只有 2 或 3 个这些测试并行运行(不同的 VS 实例),但仍然没有内存不足的异常。


推荐阅读