关于go:你的Go应用真的用了正确的-CPU-核数吗

41次阅读

共计 1322 个字符,预计需要花费 4 分钟才能阅读完成。

Go 的调度模型是 GMP,其中 G 是 goroutine,M 是线程,P 是可用的 CPU 核数。多个 G 会共用一个 M。M 作为操作系统层面上的调度单位,在执行时须要绑定到 P。如果操作系统认为的某个 Go 过程可用的 CPU 数,和该过程认为的可用的 CPU 数不统一,那么即便把 M 绑定到某个 P 上,操作系统也不肯定会执行这个线程。所以是否获取精确的可用 CPU 核数会影响 Go 的调度效率。

当用户在 k8s 中设置了资源限度:

spec:
  containers:
  - name: app_written_by_go
    resources:
      limits:
        cpu: "4"

Go 会不会辨认到可用的 CPU 为 4 呢?读者可能会认为,Go 作为一个云原生时代煊赫一时的语言,应该内置了对 k8s 的反对,所以可能辨认进去。但事实并非如此。咱们能够做个小试验:
如果启动 Go 过程时没有指定 GOMAXPROCS 环境变量,那么会以 runtime.NumCPU() 的输入作为可用的 CPU 核数(也即 P 的值)。让咱们加一下打印 NumCPU 的代码,会发现实际上输入的是 Node 上的 CPU 数目,跟 limits.cpu 无关。

runtime.NumCPU() 在 Linux 上是通过零碎调用 sched_getaffinity 实现的,但这个零碎调用只思考绑定 CPU 核的状况,不波及容器环境下 cgroup 对 CPU 资源的限度。以 docker 为例,docker run 时指定 --cpuset-cpus 能够设置容器运行时能够应用的 CPU 核的编号,但限度 CPU 的资源数次要用 --cpus=。只有前者(cpuset)是能被 sched_getaffinity 辨认的。具体见 https://docs.docker.com/config/containers/resource_constraint…。如果要想计算后者,那么须要读取机器上的 cgroup fs 文件。

有一个 Go 库反对读取 cgroup fs 并计算出精确的 GOMAXPROCS:
https://github.com/uber-go/automaxprocs
它反对两种不同的 cgroup fs(cgroup v1 和 v2):
其中 v1 是读取文件 cpu.cfs_quota_uscpu.cfs_period_us,并求两者的商。这两个文件通常位于 /sys/fs/cgroup/cpu/ 上面(automaxprocs 会读挂载信息来获取理论地位)。v2 则是读取文件 cpu.max 外面对应示意 quota 和 period 的字段,并求两者的商。除法的后果不肯定是整数,所以还有一个向下取整的过程。

存在“应该可能辨认可用 CPU 数但在容器环境下理论办不到”这种问题的并不只有 Go 一个。Nginx / Envoy 也有无奈辨认 cgroup 配置的问题。据说 Rust 和 Java(OpenJDK 实现)有专门解决过 cgroup 配置。如果你的利用合乎上面两点:

  1. 计算密集型或次要业务逻辑在若干个固定的 worker 中实现
  2. 会部署到容器环境中
    那么无妨看看所用的框架是否能正确处理 cgroup 配置。

附注:IBM 的员工已经提过一个 CPU Namespace 的设计,封装每个过程能够看失去的 CPU 信息,防止诸如 CPU 调配不统一这样的“形象透露”。不过后续就没有下文了。

正文完
 0