【问题标题】:WCF Streaming System.OutOfMemoryExceptionWCF 流式处理 System.OutOfMemoryException
【发布时间】:2015-03-06 16:42:46
【问题描述】:

在我的流式 WCF 应用程序中,我在我的服务中获得 System.OutOfMemoryExceptions 以获取大消息(使用数据读取器查询 >7GB MSSQL 表,消息甚至可能超过 7GB),而小消息工作正常。我可以观察到在执行DataReaderToExcelXml 期间内存使用量不断增长(见下文)。奇怪的是,它通常会快速增长到 2GB,保持在 2GB +-1GB 一段时间(1-5 分钟),然后再次快速增长到 ~6.5GB,这会导致异常(机器有 8GB 内存)。在这一点上,在我看来,好像 Stream 不再通过,而是被缓冲了。

我已经启用了跟踪日志记录,但它似乎因异常而停止。 DataReaderToExcelXml 函数调用是跟踪日志中的最后一个可见事件。

在 WCF 消息契约中,我确保消息只包含 Stream。在客户端,返回的 Stream 被简单地读取、写入文件流并处理。但是,当我得到异常时,我永远无法观察到正在执行的客户端代码或正在写入的文件。

我已经尝试在客户端和服务器端将maxBufferPoolSize 设置为零,如https://stackoverflow.com/a/595871/4166885 所述。没有成功。

WCF 服务端的流写入器功能:

 Public Shared Function DataReaderToExcelXml(ByRef dr As SqlDataReader) As Stream
    Dim ms As New MemoryStream
    Dim tw As New IO.StreamWriter(ms)

    For Each row As DbDataRecord In dr
            'Embed row in ExcelXml, detailed function omitted
            tw.write(row.toString()) 'row.toString is just a simplification
    End While

    tw.Flush()
    dr.Close()
    ms.Seek(0, SeekOrigin.Begin)

    Return ms
 End Function

Web.config 绑定

    <bindings>
      <basicHttpBinding>
        <binding receiveTimeout="24.00:00:00" sendTimeout="24.00:00:00"
          maxBufferPoolSize="9223372036854775807" maxReceivedMessageSize="9223372036854775807"
          messageEncoding="Mtom" transferMode="Streamed" bypassProxyOnLocal="True">
          <readerQuotas maxStringContentLength="2147483647" maxArrayLength="2147483647"
            maxBytesPerRead="2147483647" maxNameTableCharCount="2147483647" />
          <security mode="Transport">
            <transport clientCredentialType="None" />
          </security>
        </binding>
      </basicHttpBinding>
    </bindings>

App.config 绑定

    <binding name="BasicHttpBinding_IFileService" closeTimeout="00:01:00"
        openTimeout="00:01:00" receiveTimeout="24.00:00:00" sendTimeout="24.00:00:00"
        allowCookies="false" bypassProxyOnLocal="true" hostNameComparisonMode="StrongWildcard"
        maxBufferSize="2147483647" maxBufferPoolSize="2147483647"
        maxReceivedMessageSize="8589934592" messageEncoding="Mtom"
        textEncoding="utf-8" transferMode="Streamed">
        <readerQuotas maxDepth="32" maxStringContentLength="2147483647"
            maxArrayLength="2147483647" maxBytesPerRead="4096" maxNameTableCharCount="2147483647" />
        <security mode="Transport">
            <transport clientCredentialType="None" proxyCredentialType="None"
                realm="" />
            <message clientCredentialType="UserName" algorithmSuite="Default" />
        </security>
    </binding>

编辑:在我的服务上使用 .NET 内存分析器我发现,内存量的增加可以追溯到类型 byte[]。就我个人而言,这对我没有任何意义,但也许它是有用的信息。

edit2:在检查 System.OutOfMemoryException 时,我注意到它发生在 ms.Capacity = ms.length = 1073741824 = 1GB 时。因此,就在内存流将其容量翻倍之前。我仍然不确定,为什么 w3wp 一开始会消耗如此大量的内存,但现在很明显内存流触发了 Exception。

【问题讨论】:

    标签: vb.net wcf streaming out-of-memory


    【解决方案1】:

    好吧,阅读您的问题,我认为您的错误的根源是关键。错误到底在哪里?在传输中,还是在您的服务器中? 如果在 DataReaderToExcel 中引发异常,也许您可​​以考虑另一种想法(我认为这不是 WCF 问题): 到目前为止,我认为您在 Windows 窗体应用程序中也会遇到同样的问题。

    您需要内存,因此您只能使用 SqlDataReader 填充它,您必须根据范围按块查询。

    我们将开始计算“TotalRecords”:

    SELECT COUNT(*)
    FROM YOUR_TABLE  
    WHERE WATHEVER_YOU_WANT 
    

    例如,查询 10.000 条记录的块,您有 TotalRecords/10.000 个页面(查询)+1。 (示例:30.001 行,每块 10.000 行 = 4 个查询)

    迭代构建它们:

    select top **10.000** * from 
    (SELECT TOP (100) PERCENT  ROW_NUMBER() OVER (ORDER BY YOUR_TABLE_FIELD_1 DESC) ROW_PAGINATED, YOUR_TABLE_FIELD_1, YOUR_TABLE_FIELD_2 , ... , YOUR_TABLE_FIELD_N 
    FROM YOUR_TABLE  
    WHERE WATHEVER_YOU_WANT 
    ORDER BY YOUR_TABLE_FIELD_1 DESC
    ) YOUR_ALIAS
    WHERE YOUR_ALIAS.ROW_PAGINATED BETWEEN min_records_per_page AND max_records_per_page 
    )
    

    其中 min_records_per_page AND max_records_per_page 具有以下值:

    Query 1:
      min_records_per_page= 1  
      max_records_per_page  = 10000
    Query 2:
      min_records_per_page= 10001  
      max_records_per_page  = 20000
      ...
     Query N:
      min_records_per_page= (N-1)* +1
      max_records_per_page  = TotalRecords
    

    在每次迭代中,您会将每个数据行映射到 ExcelXml 类中。

    这样做,您将避免消耗所有内存,假设您将处置您使用的对象。例如,您可以每 2 次迭代使用函数 SetProcessWorkingSetSize。 在处理大量行后释放内存:

    Private Declare Auto Function SetProcessWorkingSetSize Lib "kernel32.dll" (ByVal procHandle As IntPtr, ByVal min As Int32, ByVal max As Int32) As Boolean
    
     Dim Mem As Process
     Mem = Process.GetCurrentProcess()
     SetProcessWorkingSetSize(Mem.Handle, -1, -1)
    

    然后,现在您有了一个包含映射对象的数组。第 1 部分完成。

    第 2 部分。您的客户端 WCF 可能会收到此数据。一旦要发送的数据准备就绪,请告诉我们您是否有问题。然后我们可以探索通信参数,然后我们会说“增加那个参数,或者添加这个”。

    希望对你有帮助

    【讨论】:

    • 我不确定您在第一句话中的意思。当我将 w3wp.exe 附加到调试器时,DataReaderToExcel 中的 tw.write() 会引发异常。 “使用 tw” 不会改变任何东西。 1)我看不到在哪里可以使用 dispose 或 set to nothing,因为 tw 是我唯一的对象。 2)如果我会这样做,你能详细说明吗?您的意思是将查询划分为多个较小的查询吗?那么以某种方式手动缓冲? 3)起初它似乎工作。但随后 svchosts 的内存使用量增加到数百 MB。后来它减少了,但是 w3wp 增加到了 ~5GB,这导致了 OutOfMemoryException。
    • 谢谢,但一个很大的障碍是目前没有像主键这样的唯一行标识符。更改和更新现有表实际上根本不是一种选择。你认为数据读取器是问题吗?到目前为止,我可以想象 wcf 服务在 datareader 仍然打开并读取行时不会开始流式传输。我尝试在服务端和客户端将 maxBufferPoolSizemaxBufferSize 设置为 100MB,但内存流仍会增长到 1GB。
    • 但是你可以在 sql server 查询中使用 Row_number。我稍后会尝试解释。您必须认为内存消耗是由于数据读取器造成的。 Maxbuffersize 等是通信的属性。
    • 虽然你没有行标识符,但你也可以做分页工作。我已经重新编辑了我的答案。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-11-09
    • 2018-04-04
    • 2014-06-01
    • 1970-01-01
    • 2011-03-13
    • 2011-02-13
    相关资源
    最近更新 更多