在Go语言中,Go scheduler的P
数量非常重要,因为它会极大地影响Go在运行时的表现。在目前的Go语言中,P
的数量默认是系统的CPU核数。
在容器化的环境中,Go程序所获取的CPU核数是错误的,它所获取的是宿主机的CPU核数。
即使容器和宿主机的CPU核数是共享的,但在集群中会针对每个Pod分配指定的核数,因此实际上需要的是Pod的核数,而不是宿主机的CPU核数。
Go运行时调度模型,要求M
必须与P
进行绑定,然后才能不断地在M
上循环寻找可运行的G
来执行相应的任务。
注意,
M
必须与P
进行绑定,其绑定的这个P
,要求必须是空闲状态。
在容器化的部署环境中,P
的数量由于被“错误”设置,因此拥有大量空闲的P
。可以这样理解,只要有足够多的M
,那么P
就可以都被绑定。
这时又产生了另外一个问题,M
的数量是会不断增加。在程序运行过程中,由于产生了网络I/O阻塞,导致M
会随着程序的不断执行而不断增加。最终导致Go程序的延迟加大,程序响应缓慢。
产生这个问题的本质原因是Go程序没有正确地获得所期望的CPU核数(应当获取具体分配给Pod的配额),因此解决方案有两种:
GOMAXPROCS
核数cgroup
信息,读取容器内的正确GOMAXPROCS
核数目前,Go尚没有非常完美的办法来解决这个问题,因此这里推荐使用Uber公司推出的
uber-go/maxprocs
开源库,它会在Go程序运行时根据cgroup
的挂载信息来修改GOMAXPROCS
核数,并基于一定规则选择一个最合适的数值。
使用方式如下:
package automaxprocs_test
// Importing automaxprocs automatically adjusts GOMAXPROCS.
import _ "go.uber.org/automaxprocs"
// To render a whole-file example, we need a package-level declaration.
var _ = ""
func Example() {}
只需在Go程序启动时进行引用即可,如果有特殊的需求,那么主动设置GOMAXPROCS
也是可以的。