如何解决Linux上的ASP.NET Core(Kubernetes)中的线程饥饿问题?

Mar*_*cze 10 multithreading kubernetes kestrel-http-server asp.net-core

我正在Linux上运行ASP.NET Core API,在Google Cloud中的Kubernetes上运行.

这是一个具有高负载的API,并且在每次请求时它都在执行一个执行长(1-5秒)CPU密集型操作的库.

我看到的是,在部署之后,API可以正常工作一段时间,但是在10-20分钟后它变得无法响应,甚至健康检查端点(只返回硬编码200 OK)也会停止工作并超时.(这使得Kubernetes杀死了豆荚.)

有时我也会Heartbeat took longer than "00:00:01"在日志中看到臭名昭着的错误消息.

谷歌搜索这些现象指向我"线程饥饿",因此有太多的线程池线程启动,或太多的线程阻塞等待某事,所以池中没有任何线程可以拿起ASP.NET核心请求(因此甚至健康检查端点的超时).

解决此问题的最佳方法是什么?我开始监视由ThreadPool.GetMaxThreads和返回的数字ThreadPool.GetAvailableThreads,但它们保持不变(完成端口总是1000最大和可用,并且工作者总是32767).
我应该监控其他任何财产吗?

Chr*_*att 7

一般来说,长时间运行的工作对于 Web 应用程序来说是令人厌恶的。您希望健康的网络应用程序具有亚秒级的响应时间。如果您需要执行的工作是同步的或受 CPU 限制的,则尤其如此。异步至少可以在进程期间释放线程,但是对于 CPU 密集型工作,线程会被过度占用。

You should off-load whatever you're doing to a different process and then monitor the progress. For an API, the typical approach here is to schedule the work on a different process and then immediately return a 202 Accepted, with an endpoint in the response body the client can utilize to monitor the progress/get the eventual completed result. You could also implement a webhook, which the client may register to receive notification that the process has completed, without having to constantly check on it.

Your only other option is to throw more resources at the problem. For example, you could stage multiple instances behind a load balancer, divvying requests between each instance to reduce the overall load on each.

It's also entirely possible that there's some inefficiency or issue in your code that could be corrected to either reduce the amount of time the process takes and/or the resources being consumed. As a trivial example, say if you're using something like Task.Run, you could potentially free up a ton of threads by not doing that. Task.Run should pretty much never be used within the context of a web application. However, you have not posted any code, so it's impossible to give you exact guidance there.


smn*_*ino 5

您确定您的 ASP.NET Core Web 应用程序线程用完了吗?可能只是让所有可用的 pod 资源饱和,导致 Kubernetes 杀死 pod 本身,你的 web 应用程序也是如此。

我确实在OpenShift环境中的Linux RedHat 上运行 ASP.NET Core Web API 时遇到了一个非常相似的场景,它也支持 Kubernetes 中的 pod 概念:一次调用需要大约 1 秒才能完成,在大工作负载下,它变成首先变慢然后没有响应,导致 OpenShift 关闭 pod,所以我的 web 应用程序。

可能是您的 ASP.NET Core Web 应用程序没有用完线程,特别是考虑到 ThreadPool 中有大量可用的工作线程。相反,与它们运行的​​ pod 内可用的实际毫核数相比,活动线程的数量与其 CPU 需求相结合可能太大了:实际上,在创建之后,这些活动线程对于可用 CPU 来说太多了最终被调度程序排队等待执行,而实际上只有一堆会运行。然后调度程序完成它的工作,通过频繁切换那些会使用它的线程来确保 CPU 在线程之间公平共享。至于您的情况,线程需要大量和长时间的 CPU 绑定操作,随着时间的推移,资源会饱和,Web 应用程序变得无响应。

缓解措施可能是为您的 Pod 提供更多容量,尤其是毫核,或者增加 Kubernetes 可以根据需要部署的 Pod 数量。但是,在我的特定场景中,这种方法并没有多大帮助。相反,通过将一个请求的执行时间从 1 秒减少到 300 毫秒来改进 API 本身,显着提高了 Web 应用程序的整体性能并实际解决了问题。

例如,如果您的库在多个请求中执行相同的计算,您可以考虑在您的数据结构上引入缓存,以便以少量内存成本提高速度(这对我有用),特别是如果您的操作主要是 CPU绑定,如果您对您的网络应用程序有此类请求。如果这对 API 的工作负载和响应有意义,您也可以考虑在 ASP.NET Core 中启用缓存响应。使用缓存,您可以确保您的 Web 应用程序不会两次执行相同的任务,从而释放 CPU 并降低排队线程的风险。

更快地处理每个请求将使您的 Web 应用程序不太可能充满可用 CPU 的风险,从而降低排队等待执行的线程过多的风险。