- 时序数据库
- 后端
- 数据存储
- 可观测性
- 云原生
【免费下载链接】mimir
Grafana Mimir provides horizontally scalable, highly available, multi-tenant, long-term storage for Prometheus.
导读
在 Grafana Mimir 这类需要精确感知宿主机算力资源的长时存储服务中,程序经常需要回答"这台机器到底有多少个 CPU"这一类问题。numcpus(README)正是为此而生的 Go 库:它以极小的 API 面,统一提供 Linux、Darwin、各 BSD、Solaris/Illumos 与 Windows 平台上的 CPU 数量查询能力,区分 online / offline / present / possible / configured / kernel maximum 等六种不同的 CPU 语义。阅读本文后,你将掌握 numcpus 全部公共 API 的语义与跨平台实现原理,能够直接在自己的 Go 服务中用它编写精确的 CPU 探测逻辑,并理解它在 Grafana Mimir 依赖树中承担的角色。
一、库的定位:一个函数一个语义的 CPU 计数工具
numcpus的包注释(见 numcpus.go)将其定位描述为:提供系统中 CPU 数量的信息,可获取 online(在线)、offline(离线)、present(现存)、possible(可能)、configured(已配置)或 kernel maximum(内核上限)等不同口径的 CPU 数量。
它不是运行时探针,不提供 CPU 利用率采样,也完全不依赖 cgo;所有实现都基于操作系统原生接口(sysfs、sysctl、sysconf 或 Windows API)与标准库、golang.org/x/sys完成。这使它特别适合用于:
- 服务启动时按 CPU 数量自动设置并发度、Worker 池大小;
- 容器/宿主机环境中判断可调度 CPU 规模,辅助资源配置决策;
- 需要区分"在线 CPU"与"配置 CPU"这类细微差异的系统监控与容量规划。
值得注意的是,该库的设计哲学是"每种语义一个函数",调用者按需精确选择口径,而不是笼统地拿到一个"CPU 总数"。
二、公共 API 全景:六种数量口径与四种列表接口
numcpus的完整公共 API 定义在 numcpus.go 中,全部函数签名均为func xxx() (int, error)或func ListXxx() ([]int, error)。语义对比如下:
| 函数 | 语义 | 平台支持 |
|---|---|---|
GetConfigured() | 系统中配置的 CPU 数量,与 Unix 上getconf _SC_NPROCESSORS_CONF返回值一致 | Linux、BSD、Solaris、Windows |
GetKernelMax() | 内核配置允许的最大 CPU 数量 | 仅 Linux、Windows(FreeBSD 亦支持) |
GetOffline() | 离线 CPU 数量(被热插拔下线或超过内核上限的 CPU) | 仅 Linux |
GetOnline() | 在线且被调度器调度使用的 CPU 数量 | 全部受支持平台 |
GetPossible() | 可能存在的 CPU 数量(已分配资源、若存在即可上线) | Linux、BSD、Solaris、Windows |
GetPresent() | 系统中实际存在的 CPU 数量 | Linux、BSD、Solaris、Windows |
ListOffline()/ListOnline()/ListPossible()/ListPresent() | 分别返回对应 CPU 集合的编号列表[]int | 仅 Linux |
四种List*接口在 Linux 之外不可用:源码通过构建标签隔离,numcpus_list_unsupported.go使非 Linux 平台的listOffline、listOnline、listPossible、listPresent一律返回ErrNotSupported。
库的公共错误只有一个——ErrNotSupported,当某个函数在特定平台上不受支持时返回(numcpus.go)。因此跨平台代码的正确姿势是:总是检查 error,并对ErrNotSupported给出降级策略,而不是假定函数一定可用。
三、跨平台实现原理:五套操作系统后端
3.1 Linux:直接读取 sysfs CPU 拓扑文件
Linux 后端(numcpus_linux.go)的根目录固定为/sys/devices/system/cpu,并直接读取其中的online、offline、possible、present四个属性文件以及kernel_max文件。这些文件的内容是典型的 CPU range 表示法,例如0-3,8-11表示 0~3 与 8~11 号 CPU。
countCPURange(numcpus_linux.go)负责把这种表示法解析为数量:以逗号切分各段,段内用-区分起止编号,解析出last - first + 1个 CPU;并专门处理了空文件合法的情形——例如offline文件为空即表示当前没有离线 CPU,此时返回 0 而非报错。listCPURange(numcpus_linux.go)则以同样规则展开为完整的 CPU 编号切片。
两个容易被忽略的实现细节:
getConfigured并不读取任何文件,而是Readdir扫描/sys/devices/system/cpu目录,统计所有名为cpuN(cpu后跟十进制数字)的子目录数量(numcpus_linux.go),这也正是它要与getconf _SC_NPROCESSORS_CONF对齐的缘由;getOnline优先调用sched_getaffinity(0)获取当前进程的 CPU 亲和性集合(numcpus_linux.go),只在亲和性查询失败时才回退到读取online文件。这意味着在 CPU 绑定的容器场景下,GetOnline()返回的是进程实际可用的 CPU 数,而非宿主机全部在线 CPU——这对资源受限环境下的并发度决策意义重大。
3.2 BSD / Darwin:hw.ncpu 与 hw.ncpuonline sysctl
BSD 系列后端(numcpus_bsd.go)通过golang.org/x/sys/unix读取 sysctl:
GetConfigured、GetPossible、GetPresent均读hw.ncpu;GetOnline在 NetBSD / OpenBSD 上优先读hw.ncpuonline,失败时回退hw.ncpu,其余 BSD 与 Darwin 直接读hw.ncpu(numcpus_bsd.go);GetKernelMax仅在 FreeBSD 上支持,读kern.smp.maxcpus,其余平台返回ErrNotSupported;GetOffline在所有 BSD/Darwin 平台均不支持。
3.3 Solaris / Illumos:sysconf 系统调用
Solaris 后端(numcpus_solaris.go)从/usr/include/sys/unistd.h中摘取了三个常量:_SC_NPROCESSORS_CONF = 14、_SC_NPROCESSORS_ONLN = 15、_SC_NPROCESSORS_MAX = 516,并据此调用unix.Sysconf。其中GetConfigured、GetPossible、GetPresent都对应_SC_NPROCESSORS_CONF,GetOnline对应_SC_NPROCESSORS_ONLN,GetKernelMax对应_SC_NPROCESSORS_MAX。
3.4 Windows:处理器组感知的 Win32 API
Windows 后端(numcpus_windows.go)使用windows.GetActiveProcessorCount(windows.ALL_PROCESSOR_GROUPS)与windows.GetMaximumProcessorCount(...)。通过传入ALL_PROCESSOR_GROUPS,即使系统包含多个处理器组,也能得到跨组的完整计数。GetConfigured、GetOnline、GetPossible、GetPresent共用前者,GetKernelMax用后者,GetOffline不支持。
3.5 其余平台:统一返回 ErrNotSupported
对于上述平台之外的系统,构建标签!darwin && !dragonfly && !freebsd && !linux && !netbsd && !openbsd && !solaris && !windows会命中 numcpus_unsupported.go,全部get*函数固定返回ErrNotSupported,保证库在任何 Go 构建环境下都能编译通过。
四、快速上手:完整可运行示例
原文档给出的用法示例(README)可以直接作为工程起点。下面是在其基础上补充了全部 API 与降级处理的最小演示程序:
package main import ( "fmt" "os" "github.com/tklauser/numcpus" ) func main() { // 在线且被调度器调度的 CPU 数量 online, err := numcpus.GetOnline() if err != nil { fmt.Fprintf(os.Stderr, "GetOnline: %v\n", err) } fmt.Printf("online CPUs: %v\n", online) // 可能存在的 CPU 数量(已分配资源) possible, err := numcpus.GetPossible() if err != nil { fmt.Fprintf(os.Stderr, "GetPossible: %v\n", err) } fmt.Printf("possible CPUs: %v\n", possible) // 已配置 CPU 数量,等价于 getconf _SC_NPROCESSORS_CONF configured, err := numcpus.GetConfigured() if err != nil { fmt.Fprintf(os.Stderr, "GetConfigured: %v\n", err) } fmt.Printf("configured CPUs: %v\n", configured) // 离线 CPU 数量(仅 Linux 支持,其他平台返回 ErrNotSupported) offline, err := numcpus.GetOffline() if err == numcpus.ErrNotSupported { fmt.Println("GetOffline: not supported on this platform") } else if err != nil { fmt.Fprintf(os.Stderr, "GetOffline: %v\n", err) } else { fmt.Printf("offline CPUs: %v\n", offline) } }引入依赖:
go get github.com/tklauser/numcpus在 Linux 上运行上述程序,你会看到形如online CPUs: 8、possible CPUs: 8、configured CPUs: 8的输出;若在容器内且进程被限制到部分 CPU,GetOnline()会反映进程实际可用的 CPU 数(Linux 后端优先走亲和性探测)。
五、在 Grafana Mimir 仓库中的实际角色
numcpus出现在 Grafana Mimir 的依赖树中,但不是被 Mimir 代码直接调用。在 go.mod 中它以github.com/tklauser/numcpus v0.11.0 // indirect标记为间接依赖,真正引用它的是另一个间接依赖github.com/tklauser/go-sysconf:
- vendor/github.com/tklauser/go-sysconf/sysconf_linux.go 在处理
_SC_NPROCESSORS_ONLN时调用numcpus.GetOnline(); - 同文件 sysconf_linux.go 在处理
_SC_NPROCESSORS_CONF时调用numcpus.GetConfigured()。
即 numcpus 在 Mimir 中承担的是"为 sysconf 兼容层提供 Linux CPU 计数"的底层职能。从更宏观的视角看,Mimir 自身的算力感知更多依赖 Go 标准库的runtime.NumCPU()与runtime.GOMAXPROCS(0):
- cmd/mimir/main.go 中的
clampGOMAXPROCS()会在环境变量设置的GOMAXPROCS高于runtime.NumCPU()时将其钳制回 NumCPU 并输出告警日志; - 多个组件的并发度默认值都跟随
GOMAXPROCS,例如 pkg/ingester/ingester.go 的ingester.compute-workers参数在取 0 时使用GOMAXPROCS,pkg/util/workerpool/pool.go 的 worker 池大小默认同样取自GOMAXPROCS。
理解 numcpus 的 online/possible 语义差异,有助于你更精确地解读这类"并发度由 CPU 数量推导"的服务端配置逻辑。
六、选型建议与注意事项
- 按语义选函数,不要一律取"总数":统计 worker 规模优先
GetOnline()(真正可调度);评估硬件规模用GetPresent()或GetConfigured();做热插拔监控才需要GetOffline()与GetKernelMax()。 - 始终处理 error:非 Linux 平台上
GetKernelMax、GetOffline及全部List*都返回ErrNotSupported,跨平台程序必须有降级分支。 - 容器场景注意 Linux 特例:
GetOnline()优先返回进程亲和性集合大小,因此在容器限制 CPU 时会得到受限值,这可能正是你想要的,也可能出乎意料,需结合部署环境判断。 - 与
runtime.NumCPU()互补而非替代:runtime.NumCPU()只能给出进程可用的逻辑 CPU 数,无法区分 online / offline / present / possible 这些细粒度口径;需要这些区分度时再引入 numcpus。
七、深入阅读
- 完整 API 与文档注释:numcpus.go
- Linux 实现(sysfs 解析与亲和性探测):numcpus_linux.go
- BSD / Darwin sysctl 实现:numcpus_bsd.go
- Solaris / Illumos 实现:numcpus_solaris.go
- Windows 实现:numcpus_windows.go
- 在 Mimir 依赖树中的调用链:sysconf_linux.go
- 原文档在仓库中的位置:README,其中 References 指向的 Linux 内核 sysfs CPU 属性文档与 CPU 拓扑文档,是理解
/sys/devices/system/cpu各属性文件语义的权威参考资料。
- 时序数据库
- 后端
- 数据存储
- 可观测性
- 云原生
【免费下载链接】mimir
Grafana Mimir provides horizontally scalable, highly available, multi-tenant, long-term storage for Prometheus.
相关推荐
Grafana Tempo 依赖解析:numcpus 跨平台 CPU 计数库原理与实践指南
Grafana Tempo 依赖解析:numcpus 跨平台 CPU 计数库原理与实践指南 本篇技术指南聚焦 Grafana Tempo 仓库内 vendore
后端可观测性链路追踪跨平台 CPU 数量检测:深入解析 numcpus Go 库的实现原理与实战用法
跨平台 CPU 数量检测:深入解析 numcpus Go 库的实现原理与实战用法 导读 在高并发扫描器、资源池调度与系统监控类程序中,准确获取宿主机的 CPU
网络安全漏洞扫描渗透测试应用安全用 Go 精确获取系统 CPU 数量:numcpus 库的六大指标与跨平台实现解析
用 Go 精确获取系统 CPU 数量:numcpus 库的六大指标与跨平台实现解析 本文基于 cloudpods 仓库所 vendored 的 numcpus
后端云原生运维虚拟化人工智能AI 应用
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考