大型的HTTPResponseMessage导致.NET Core服务器进程耗尽内存

4
我是一名有用的助手,可以为您翻译文本。

我有一个C# .NET 2.2 Web服务器进程,它公开了一个API。当请求到达时,服务器需要向数据库API发出自己的HTTP请求。根据查询,数据库的响应可能非常大,在某些情况下,这足以导致我的.NET进程在日志中崩溃并显示(内存配额超限)

发送请求的代码如下:

string endpoint_url = "<database service url>";
var request_body = new StringContent(query, Encoding.UTF8, "<content type>");
request_body.Headers.ContentType.CharSet = "";
try {
    var request_task = Http.client.PostAsync(endpoint_url, request_body);
    if (await Task.WhenAny(request_task, Task.Delay(timeoutSeconds*1000)) == request_task) {
        request_task.Result.EnsureSuccessStatusCode();
        var response = await request_task.Result.Content.ReadAsStringAsync();
        JObject json_result = JObject.Parse(response);
        if (json_result["errors"] is null) {
            return json_result;
        } else {
            // return error
        }
    } else {
        // return timeout error
    }
} catch(Exception e) {
    // return error
}

我的问题是:当查询返回大量响应时,保护我的Web服务免于崩溃的最佳方法是什么?.NET Core最佳实践建议我不应该将响应体整个加载到字符串中,但并没有真正提出替代方案。
我希望能够优雅地失败并向客户端返回错误,而不是导致.NET服务停机,因此设置某种响应大小限制将起作用。不幸的是,所涉及的数据库服务没有返回Content-Length头,因此我无法检查。
目前,我的Web服务器只有512MB的可用内存,我知道这不多,但我担心无论我有多少可用内存,这种错误都可能发生在大量响应上。我的主要关注点是保证我的.NET服务不会崩溃,不管来自数据库服务的响应大小如何。

抱歉,我不理解。如果您想限制数据库响应大小,那么为什么分享一个与问题无关的代码?我们正在谈论什么类型的数据库?是否考虑了分页?当数据库不能/不应该返回相当大量的数据时,需要采取什么行动? - Peter Csala
嗨@PeterCsala。我不是在问如何限制数据库响应大小(这对我来说不是选项),而是更普遍地询问如何优雅地处理一般情况下从HTTP调用中返回的大型响应大小。分页是解决我的问题的好建议,但它仍然不能保证我的.NET服务器不会耗尽内存(例如,一个数据点理论上可能超过内存限制)。如果可能的话,我希望处理这个问题,而不假设HTTP请求的另一端有什么东西。 - Rob Streeting
我希望抛出一个异常(或以某种形式引发错误),以便我可以捕获它并向我的服务客户端发送错误代码。我不希望我的Web服务器在数据库的HTTP响应过大时崩溃并重新启动。 - Rob Streeting
3个回答

1

您可以使用最简单的方法,根据返回的行数进行决策。

如果您正在使用ExecuteReader,它将不会返回受影响的行,但是您可以通过简单地返回两个结果集来克服此限制。第一个结果集将具有一个带有单个列的单行,告诉您行数,并基于此决定是否调用NextResult并处理请求的数据。

如果您正在使用存储过程,则可以使用输出参数来指示检索到的行数。通过使用@@ROWCOUNT变量或ROWCOUNT_BIG()函数。再次,您可以根据该数据进行分支。

这些解决方案的优点是,如果记录数量超过了可用空间,您无需读取任何记录。

这些解决方案的缺点是,确定阈值可能很难,因为它可能取决于查询本身,其中一个(或多个)参数,表大小等。


1
如果Http.client是一个HttpClient,您可以通过其MaxResponseContentBufferSize属性限制它在中止操作并抛出异常之前读取的最大数据量。默认情况下,它设置为2Gb,这就解释了为什么如果服务器只有512Mb的RAM,它会崩溃,因此您可以将其设置为10/20Mb,并在溢出时处理异常。

谢谢!听起来这可以胜任。我会研究一下的。 - Rob Streeting

1
你绝对不应该创建一个无限制的字符串,它可能比你的堆大小还要大,但这比仅仅提供建议更为复杂。正如其他人所指出的,整个系统需要协同工作,以便在有限的内存占用下返回大量结果。
对于你的直接问题——如果响应内容无法放入内存,我该如何发送错误信息——最简单的答案是创建一个有限的“最大”大小缓冲区,并从响应中仅读取那么多数据。如果不适合你的缓冲区,则太大了,你可以返回一个错误。
但总的来说,这是一种较差的设计,因为“最大”是不可能静态派生的——它取决于服务器负载。
更好的答案是避免在将整个结果发送到客户端之前缓冲整个结果,而是将结果流式传输到客户端——读取一个缓冲区的数据并将其写出到客户端,或者将该缓冲区的某些处理形式写出到客户端。但这需要后端API、你的服务和可能的客户端之间的一些协同工作。
如果你的服务必须解析完整的对象——就像你展示的Json.Parse一样——那么你很可能需要重新思考你的设计。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接