理解Go、容器以及Linux调度器
创始人
2025-07-09 03:20:56
0

Go开发的应用程序通常部署在容器中。在容器中运行时,重要的一点是要设置CPU限制以确保容器不会耗光主机上的所有CPU。但Go运行时不知道容器上设置的CPU限制,因此有可能会把所有可用的CPU都用光,从而造成应用延迟很高。这个问题曾经困扰过我,在这篇文章中,我将解释发生了什么以及如何修复。

Go垃圾收集器是如何工作的

这是对Go垃圾收集器(GC)的概要介绍,想要更深入了解,建议阅读Go文档[2]以及Will Kennedy的系列文章[3]。

绝大多数情况下,Go运行时在执行程序的同时执行垃圾收集,这意味着GC会与程序同时运行。然而,在GC过程中有两个点需要Go运行时暂停所有Goroutine,从而确保数据完整性。在GC标记阶段(Mark Phase)之前,运行时将暂停所有Goroutine,用以启用写屏障(write barrier),确保在此之后创建的任何对象都不会被GC,这个阶段称为扫描终止(Sweep Termination)。在标记阶段完成后,还有一个STW(stop the world)阶段,被称为标记终止(Mark Termination),并且也是删除写屏障的过程。整个流程通常需要几十微秒。

我创建了一个简单的web应用,分配了大量内存,并使用以下命令在一个限制为4个CPU核的容器中运行,源代码在Github[4]上。

docker run --cpus=4 -p 8080:8080 $(ko build -L main.go)

值得注意的是,docker CPU限制是硬性限制。可以设置--CPU-shares,表示只在主机CPU受限时强制执行。这意味着如果主机有空闲容量,容器可以使用超出分配的CPU核。但是如果主机资源受限,那么应用程序也将受到限制。

可以使用runtime/trace[5]包收集trace,然后用go tool trace对其进行分析。下面的trace显示了在我的机器上捕获的一个GC周期,可以看到在Proc 5中STW阶段的扫描终止和标记终止。

这个GC周期只花了不到2.5ms,但我们在STW阶段花费了近10%的时间。这是相当长的一段时间,特别是对于延迟敏感应用来说。

Linux调度器

完全公平调度程序(Complete Fair Scheduler, CFS)[6]是在Linux 2.6.23中引入的,在2023年10月份发布的Linux 6.6之前一直是默认调度程序,很可能你正在使用CFS。

CFS是一个比例共享调度器[7],意味着进程权重与允许使用的CPU内核数量成正比。例如,如果允许一个进程使用4个CPU核,那么它的权重将为4。如果一个进程被允许使用2个CPU核心,它的权重将为2。

CFS通过分配一小部分CPU时间来实现,一个4核系统每秒钟有4秒的CPU时间可以分配。当我们为容器分配多个CPU内核时,实际上是要求Linux调度器给它n个CPU的时间。

在上面的docker run命令中,指定了4个CPU,意味着容器每秒将获得4秒的CPU时间。

问题

当Go运行时启动时,为每个CPU内核创建一个操作系统线程。这意味着如果有一个16核的机器,Go运行时将创建16个操作系统线程,不管任何CGroup CPU限制。然后Go运行时使用这些操作系统线程来调度程序。

问题是Go运行时不知道CGroup的CPU限制,而是在所有16个操作系统线程上调度goroutine,意味着Go运行时预计每秒能够使用16秒的CPU时间。

由于Go运行时需要在等待Linux调度器调度的线程上停止gooutine,因此将面临长时间的STW时间,因为一旦容器使用超过了CPU配额,线程就不会被调度。

解决方案

Go通过设置GOMAXPROCS环境变量限制运行时将创建的CPU线程数量。这一次,使用以下命令来启动容器:

docker run --cpus=4 -e GOMAXPROCS=4 -p 8080:8080 $(ko build -L main.go)

下面是从与上面相同的应用程序捕获的trace,现在使用与CPU配额匹配的GOMAXPROCS环境变量。

在这个trace中,尽管负载完全相同,但垃圾收集时间要短得多。GC周期小于1ms,STW时间为26μs,约为无限制时的1/10。

GOMAXPROCS应该设置为容器允许使用的CPU核数,通常情况应该向下取整,如果分配的CPU内核少于1个,则向上取整。可以用GOMAXPROCS=max(1, floor(cpu))来计算。Uber开源了一个库automaxprocs[8]来自动从容器的cgroups中计算这个值。

有一个Github问题[9]支持将这个特性添加到Go运行时中,使其开箱即用,希望最终会被Go运行时接受!

结论

在容器化应用程序中运行Go时,设置CPU限制非常重要。通过设置合理的GOMAXPROCS值或使用像automaxprocs这样的库,确保Go运行时意识到这些限制也很重要。

参考资料

[1]Go, Containers, and the Linux Scheduler: https://www.riverphillips.dev/blog/go-cfs

[2]Go文档: https://tip.golang.org/doc/gc-guide

[3]Garbage Collection In Go: https://www.ardanlabs.com/blog/2018/12/garbage-collection-in-go-part1-semantics.html

[4]示例代码: https://github.com/RiverPhillips/go-cfs-blog

[5]runtime/trace package: https://golang.org/pkg/runtime/trace

[6]完全公平调度程序(Complete Fair Scheduler, CFS): https://docs.kernel.org/scheduler/sched-design-CFS.html

[7]Proportional share scheduling: https://en.wikipedia.org/wiki/Proportional_share_scheduling

[8]automaxprocs: https://github.com/uber-go/automaxprocs

[9]Github问题: https://github.com/golang/go/issues/33803

相关内容

热门资讯

如何允许远程连接到MySQL数... [[277004]]【51CTO.com快译】默认情况下,MySQL服务器仅侦听来自localhos...
如何利用交换机和端口设置来管理... 在网络管理中,总是有些人让管理员头疼。下面我们就将介绍一下一个网管员利用交换机以及端口设置等来进行D...
施耐德电气数据中心整体解决方案... 近日,全球能效管理专家施耐德电气正式启动大型体验活动“能效中国行——2012卡车巡展”,作为该活动的...
20个非常棒的扁平设计免费资源 Apple设备的平面图标PSD免费平板UI 平板UI套件24平图标Freen平板UI套件PSD径向平...
德国电信门户网站可实时显示全球... 德国电信周三推出一个门户网站,直观地实时提供其安装在全球各地的传感器网络检测到的网络攻击状况。该网站...
为啥国人偏爱 Mybatis,... 关于 SQL 和 ORM 的争论,永远都不会终止,我也一直在思考这个问题。昨天又跟群里的小伙伴进行...
《非诚勿扰》红人闫凤娇被曝厕所... 【51CTO.com 综合消息360安全专家提醒说,“闫凤娇”、“非诚勿扰”已经被黑客盯上成为了“木...
2012年第四季度互联网状况报... [[71653]]  北京时间4月25日消息,据国外媒体报道,全球知名的云平台公司Akamai Te...