Go、容器以及Linux调度器

在容器中运行Go应用程序时,需要设置合理的GOMAXPROCS,从而避免调度中因为资源不足而造成STW。原文: Go, Containers, and the Linux Scheduler

alt

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

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

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

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

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

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

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

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

alt

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

Linux调度器

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

CFS是一个比例共享调度器[4],意味着进程权重与允许使用的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环境变量。

alt

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

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

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

结论

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


你好,我是俞凡,在Motorola做过研发,现在在Mavenir做技术工作,对通信、网络、后端架构、云原生、DevOps、CICD、区块链、AI等技术始终保持着浓厚的兴趣,平时喜欢阅读、思考,相信持续学习、终身成长,欢迎一起交流学习。为了方便大家以后能第一时间看到文章,请朋友们关注公众号"DeepNoMind",并设个星标吧,如果能一键三连(转发、点赞、在看),则能给我带来更多的支持和动力,激励我持续写下去,和大家共同成长进步!

参考资料
[1]

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

[2]

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

[3]

runtime/trace package: https://golang.org/pkg/runtime/trace

[4]

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

本文由 mdnice 多平台发布

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/433079.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

jnekins使用docker部署springboot项目

创建Dockerfile FROM openjdk:21 VOLUME /tmp ADD springboot3-0.0.1-SNAPSHOT.jar app.jar EXPOSE 8888 ENTRYPOINT ["java","-jar","/app.jar","--spring.cloud.nacos.server-addr192.168.8.11:8848"] 新建一个【自由风格】项目 选…

Mac网线上网绿联扩展坞连接网线直接上网-无脑操作

声明:博主使用的绿联扩展坞 以下为绿联扩展坞Mac网线使用方法 1.首先需要下载电脑对应版本的驱动 直接点击即可下载 2. 下载好以后 解压 点进去 对应版本 博主直接使用最新的12-14 3. 安装包好了以后 会提示重启电脑 此时拔掉扩展坞 再重启动 拔掉扩展坞 再重启…

CodeGPT

GitCode - 开发者的代码家园 gitcode.com/ inscode.csdn.net/liujiaping/java_1706242128563/edit?openFileMain.java&editTypelite marketplace.visualstudio.com/items?itemNameCSDN.csdn-codegpt&spm1018.2226.3001.9836&extra%5Butm_source%5Dvip_chatgpt_c…

Android发展历程

安卓基于Linux内核,Linux内核相当于房屋的地基 开源不等于免费,不能商用 安卓一般每半年小更新,一年大更新 对应API相当于别名 现在安卓安全性越来越高,性能越来越快,越更新版本,说明安卓越完善 下载网址 …

盘古信息IMS OS 数垒制造操作系统+ 产品及生态部正式营运

启新址吉祥如意,登高楼再谱新篇。2024年1月22日,广东盘古信息科技股份有限公司新办公楼层正式投入使用并举行了揭牌仪式,以崭新的面貌、奋进的姿态开启全新篇章。 盘古信息总部位于东莞市南信产业园,现根据公司战略发展需求、赋能…

【mac】 配合鼠标放大缩小页面\通过ctrl或cmd键缩放页面

win转mac后,一直苦于页面的操作习惯,终于BBT这个工具可以通过添加滚轮手势来实现快捷键缩放页面 顶部配置触发选择“普通鼠标” 然后设置触发操作

【LUA】mac状态栏添加天气

基于网络上的版本修改的,找不到出处了。第一个摸索的lua脚本,调了很久。 主要修改:如果风速不大,就默认不显示,以及调整为了一些格式 local urlApi http://.. --这个urlApi去申请个免费的就可以了 然后打开对应的json…

mybatis-plus常用使用方法

** mybaits-plus常用使用方法 ** 常用三层分别继承方法 1.1mapper层&#xff08;接口定义层&#xff09;可以用BaseMapper<> 例如&#xff1a; 1.2.里面常用的封装方法有 1.3常用方法介绍 【添加数据&#xff1a;&#xff08;增&#xff09;】int insert(T entity);…

念念不忘智能编程,必有回响CodeArts Snap

开发者的碎碎念 之前在【我与ModelArts的故事】的文章里&#xff0c;分享过我学习新技术的经历&#xff0c;主要有&#xff1a; 自主学习&#xff0c;比如自学Python&#xff1b;借助华为云的产品边用边学。 在围着"编程学习"这座城池&#xff0c;外围来来回回转了…

matlab appdesigner系列-图窗3-菜单

菜单&#xff0c;常用来作为软件界面上方的菜单栏&#xff0c;可用来集成多种功能 示例&#xff1a;创建菜单栏的file选项&#xff0c;设置open&#xff0c;save&#xff0c;exit三个选项 操作步骤&#xff1a; 1&#xff09;将菜单、图像拖拽到画布上&#xff0c;并修改对应…

【秒剪】如何添加基础文字?

Step1&#xff1a;打开剪辑页&#xff0c;点击下方【文字贴图】 undefined Step2&#xff1a;点击【花字贴图】&#xff0c;选择【基础文字】 undefined Step3&#xff1a;输入文字内容&#xff0c;即可添加到视频中 undefined Step4&#xff1a;添加好文字后&#xff0c;你…

【GTK】GtkApplicationWindow/GtkApplication

一、GtkApplicationWindow 在GTK&#xff08;GIMP Toolkit&#xff09;应用程序开发中&#xff0c;并不是每个GTK程序都必须有一个GtkApplicationWindow&#xff0c;但大多数基于GTK的图形界面应用程序都会使用GtkApplicationWindow&#xff0c;因为它是一个方便的类&#xff…