为什么 k8s 上 0.5 核的 pod 这么忙?

  • 我们的技术总监在我写广告合并请求的业务时,和我说了一句现在的服务是不是都是运行在 0.5核的节点上,需要注意设置一下参数

  • 然后我回去看了一下,我们的 golang 部分服务是运行在 k8s0.5核pod, 然后跑在多台 8核的物理节点上
  • 然后程序中可以通过以下命令打印出当前的 GOMAXPROCS, 服务虽然运行在 pod 上,但打印的是实际的宿主机的核心数
package main

import (
    "fmt"
    "runtime"
)

func main() {

    // runtime.GOMAXPROCS(1) : 设置为 1
    fmt.Printf("当前: %d", runtime.GOMAXPROCS(0))
    // 8
}
  • 先说解决方案,直接使用 github.com/uber-go/automaxprocs 这个库获取到实际的核心数,然后设置
  • 修改完之后,我们的服务负载就下降了很多

1.png

1.png

why#

  • GMP 设计思想 G 代表 goroutine 协程,M 代表 machine 线程,P 代表 processor 处理器;P 包含了运行 G 所需要的资源,M 想要运行 goroutine 必须先获取 P

  • 为什么修改 GOMAXPROCS 参数可以更高效的运行呢?简单的来说,就是本身容器只有 0.5 核,但是却设置了 GOMAXPROCS=8, 导致会创建出 8P, 每个 P 由不同的 M 管理

  • 所以当 GOMAXPROCS 大于核心数量的时候,会导致线程不断的切换,然后 cpu 有一部分时间被切换占用了 (设置为 cpu 的核心数可以减少切换,但还是会有切换的场景)

    2.jpg

Q 那么设置 GOMAXPROCS 等于 1 的时候,什么时候会出现线程切换?是不是无法高并发呢?#

  • 答案是可以的,虽然同是只能处理 1 个任务,但是 cpu 实在是太快了。并且网络 io(大部分 web 服务都是属于网络 io) 不占用 cpu 时间
  1. 根据 G-P-M 调度模型,GOMAXPROCS 等于 1,
    1. 一个 P 指为 P1 运行 (假设 P1 里的本地队列有 G0,G1,G2 三个 goroutine),
    2. 创建新的 M1 绑定到 P1
  2. G0 里遇到以下场景
    1. 出现系统调用,文件 io 阻塞的时候
    2. 会把当前的线程 P 绑定的 M1 线程去交给系统调度,
    3. 然后从休眠线程队列 / 创建新的线程 M2
    4. 然后绑定到 P1, 继续调度 P1 后面的 G1,G2
  3. M1 的阻塞调用结束后,会优先返回 P1, 但是 P1 已经被 M2 占用,然后从空闲队列 P 获取,但是我们只有一个 P, 也获取失败,就会把 G0 放到全局队列中,等待 P1 之后获取
  • 3.png

PS#

  • 网络 io 不会造成阻塞,因为 golang 在网络请求时,其后台通过 kqueue(MacOS),epoll(Linux)或 iocp(Windows 来实现 IO 多路复用
  • 所以网络 io 密集的服务,即使核心数量不多,golang 也能处理高并发请求

引用#

golang 系统调用与阻塞处理

本作品采用《CC 协议》,转载必须注明作者和本文链接
当神不再是我们的信仰,那么信仰自己吧,努力让自己变好,不辜负自己的信仰!
讨论数量: 4

啥?cpu 还能 0.5 核这样用?

1年前 评论
seth-shi (楼主) 1年前
overfalse (作者) 1年前
singee 1年前

未填写
文章
42
粉丝
158
喜欢
713
收藏
347
排名:30
访问:22.2 万
私信
所有博文
社区赞助商