在托管在 AWS ECS FARGATE(docker)上的 dotnet core 2.2 REST 服务中,我经常(每 30-60 分钟)有一个实例崩溃并出现System.OutOfMemoryException
,即使 ECS 报告的最大内存使用率为 11%(总计 16GB)。崩溃始终来自任务计划程序(下面的堆栈跟踪)。它只发生在生产中。
我正在寻求有关如何解决此问题的建议。(编辑:我不认为这实际上是一个内存不足的问题,除非Thread:StartInternal()
可以突然使用16GB的90%,速度比AWS监控工具注册它的速度快
)该应用程序在 Windows 10 上本地运行,我还尝试通过维持 100 个并发请求在单独的 ECS 集群(我们的测试集群)上重现,但没有运气。 服务的一个终结点接收 99%+ 的请求。基本操作为:
- 尝试使用
async/await
在MongoDB数据库中查找一些文档(基于输入) - 从 WCF 提取数据(同步,见下文)
- 对于某些结果,使用外部 URL 获取数据(有时很慢),
System.New.WebRequest
使用async/await
- 返回结果
WCF 服务称为同步,因为我们使用的是 WCF 之上的客户端库,这不是异步安全的。但是,结果在MemoryCache
中存储 1 分钟,并且到期时重新获取使用 AsyncEx.AsyncMonitor 进行保护,因此只允许一个调用方更新缓存,如下所示:
using( await _monitor.EnterAsync( ) )
{
if( !Cache.TryGetValue( "UserLookup", out LookupUsers lookupUsers ) )
{
lookupUsers = await GetCachedUsers( ssoToken );
Cache.Set( "UserLookup", lookupUsers, TimeSpan.FromMinutes( 1 ) );
}
return lookupUsers;
}
GetCachedUsers()
这样做:
var users = await Task.Run( ( ) => client.Proxy.ListUsers( new ListUsersInput { } ) );
并且在超时或其他问题的情况下返回默认值。
操作的入口点如下:
[Route( "get-content" )]
[HttpPost]
public async Task<RemoteGetContentResult> GetContent( [FromBody]RemoteGetContentInput input )
{
// input validation
var c = Interlocked.Increment( ref _concurrency );
try
{
// log value of _concurrency
return await _provider.GetContentExAsync( input );
}
finally
{
Interlocked.Decrement( ref _concurrency );
}
}
记录的并发级别通常为 10-30,但可以达到 100(当有许多外部 http 提取时)。
以下是我在 AWS ECS 日志中看到的堆栈跟踪:
2019-07-10T06:22:39.554Z Unhandled Exception: System.Threading.Tasks.TaskSchedulerException: An exception was thrown by a TaskScheduler. ---> System.OutOfMemoryException: Exception of type 'System.OutOfMemoryException' was thrown.
2019-07-10T06:22:39.554Z at System.Threading.Thread.StartInternal()
2019-07-10T06:22:39.554Z at System.Threading.Tasks.Task.ScheduleAndStart(Boolean needsProtection)
2019-07-10T06:22:39.554Z --- End of inner exception stack trace ---
2019-07-10T06:22:39.554Z at System.Threading.Tasks.Task.ScheduleAndStart(Boolean needsProtection)
2019-07-10T06:22:39.554Z at System.Threading.Tasks.Task.InternalStartNew(Task creatingTask, Delegate action, Object state, CancellationToken cancellationToken, TaskScheduler scheduler, TaskCreationOptions options, InternalTaskOptions internalOptions)
2019-07-10T06:22:39.554Z at System.Runtime.IOThreadScheduler.ScheduleCallbackHelper(SendOrPostCallback callback, Object state)
2019-07-10T06:22:39.554Z at System.Runtime.IOThreadScheduler.ScheduleCallbackNoFlow(SendOrPostCallback callback, Object state)
2019-07-10T06:22:39.554Z at System.Runtime.CompilerServices.YieldAwaitable.YieldAwaiter.System.Runtime.CompilerServices.IStateMachineBoxAwareAwaiter.AwaitUnsafeOnCompleted(IAsyncStateMachineBox box)
2019-07-10T06:22:39.554Z at System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1.AwaitUnsafeOnCompleted[TAwaiter,TStateMachine](TAwaiter& awaiter, TStateMachine& stateMachine)
2019-07-10T06:22:39.554Z --- End of stack trace from previous location where exception was thrown ---
2019-07-10T06:22:39.554Z at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state)
2019-07-10T06:22:39.554Z --- End of stack trace from previous location where exception was thrown ---
2019-07-10T06:22:39.554Z at System.Threading.ThreadPoolWorkQueue.Dispatch()
更新: 我每 5 秒添加一些关于该过程的额外日志记录。在18:30:16.741Z,它记录了:
2019-07-10T18:30:16.741Z concurrency: 4 proc thread cnt: 29 avail worker threads: 32,766 avail compl port threads: 1,000 ws: 1,733,996,544 peak ws: 0
所以 16GB 中的 ~1.7GB 工作集。(由于某种原因,峰值 WS 始终为 0,但我看到的最大值是 2,053,316,608 字节)。 4 秒后,它引发 OOM 异常:
2019-07-10T18:30:20.630Z Unhandled Exception: System.Threading.Tasks.TaskSchedulerException: An exception was thrown by a TaskScheduler. ---> System.OutOfMemoryException: Exception of type 'System.OutOfMemoryException' was thrown.
事实证明,我们正在使用一个库,该库使用HttpClient
而不处理它,从而泄漏套接字。
我们已经在 Windows 上使用这个库一段时间了,但显然套接字最终被终结器关闭,但在 Linux 上没有。
我最终在一台普通的 Linux 机器上运行了该应用程序,从而更容易监控操作系统。事实证明,这个命令
$ lsof -p <PID>
像这样返回数千行
dotnet 15613 ec2-user 215u sock 0,8 0t0 4968805 protocol: TCP
dotnet 15613 ec2-user 219u sock 0,8 0t0 4968844 protocol: TCP
dotnet 15613 ec2-user 220u sock 0,8 0t0 4968236 protocol: TCP
dotnet 15613 ec2-user 221u sock 0,8 0t0 4968247 protocol: TCP
...
将HttpClient
用法转换为单例解决了该问题。