在 Go 的个人项目中,该项目从 Bovespa 获取金融资产信息。
该系统充分利用了 goroutine 的并发性和并行性,每 8 秒更新一次资产信息(以及业务计算)。
最初,没有出现错误或警告,但我注意到一些 goroutine 的执行时间比其他 goroutine 更长。
更具体地说,虽然p99时间为0.03 ms,但在某些时候,它增加到0.9 ms。这促使我进一步调查这个问题。
我发现我使用的是信号量 goroutine 池,它是基于 GOMAXPROCS 变量创建的。
然而,我意识到这种方法有一个问题。
当我们使用 GOMAXPROCS 变量时,它无法正确捕获容器中可用的核心数量。如果容器的可用核心数少于 VM 的总数,则它会考虑 VM 的总数。例如,我的虚拟机有 8 个可用核心,但容器只有 4 个。这导致创建 8 个 goroutine 同时运行,导致限制。
经过一夜的大量研究,我发现 Uber 开发的一个库可以更有效地自动调整 GOMAXPROCS 变量,无论它是否在容器中。事实证明,该解决方案极其稳定且高效:automaxprocs
自动设置 GOMAXPROCS 以匹配 Linux 容器 CPU 配额。
go get -u go.uber.org/automaxprocs
import _ "go.uber.org/automaxprocs"
func main() {
// Your application logic here.
}
数据来自 Uber 的内部负载均衡器。我们以 200% CPU 配额(即 2 个核心)运行负载均衡器:
GOMAXPROCS | RPS | P50(毫秒) | P99.9(毫秒) |
---|---|---|---|
1 | 28,893.18 | 1.46 | 19.70 |
2(等于配额) | 44,715.07 | 0.84 | 26.38 |
3 | 44,212.93 | 0.66 | 30.07 |
4 | 41,071.15 | 0.57 | 42.94 |
8 | 33,111.69 | 0.43 | 64.32 |
默认 (24) | 22,191.40 | 0.45 | 76.19 |
当 GOMAXPROCS 增加到 CPU 配额以上时,我们看到 P50 略有下降,但看到 P99 显着增加。我们还看到处理的总 RPS 也在下降。
当 GOMAXPROCS 高于分配的 CPU 配额时,我们还看到了明显的限制:
$ cat /sys/fs/cgroup/cpu,cpuacct/system.slice/[...]/cpu.stat nr_periods 42227334 nr_throttled 131923 throttled_time 88613212216618
一旦 GOMAXPROCS 减少到与 CPU 配额相匹配,我们就没有看到 CPU 限制。
使用该库后,问题得到解决,现在p99时间一直保持在0.02ms。这一经验强调了并发系统中可观察性和分析的重要性。
以下是一个非常简单的示例,但展示了性能差异。
使用Go的原生测试和benckmak包,我创建了两个文件:
benchmarking_with_enhancement_test.go:
package main import ( _ "go.uber.org/automaxprocs" "runtime" "sync" "testing" ) // BenchmarkWithEnhancement Função com melhoria, para adicionar o indice do loop em um array de inteiro func BenchmarkWithEnhancement(b *testing.B) { // Obtém o número de CPUs disponíveis numCPUs := runtime.NumCPU() // Define o máximo de CPUs para serem usadas pelo programa maxGoroutines := runtime.GOMAXPROCS(numCPUs) // Criação do semáforo semaphore := make(chan struct{}, maxGoroutines) var ( // Espera para grupo de goroutines finalizar wg sync.WaitGroup // Propriade mu sync.Mutex // Lista para armazenar inteiros list []int ) // Loop com mihão de indices for i := 0; ibenchmarking_without_enhancement_test.go:
package main import ( "runtime" "sync" "testing" ) // BenchmarkWithoutEnhancement Função sem a melhoria, para adicionar o indice do loop em um array de inteiro func BenchmarkWithoutEnhancement(b *testing.B) { // Obtém o número de CPUs disponíveis numCPUs := runtime.NumCPU() // Define o máximo de CPUs para serem usadas pelo programa maxGoroutines := runtime.GOMAXPROCS(numCPUs) // Criação do semáforo semaphore := make(chan struct{}, maxGoroutines) var ( // Espera para grupo de goroutines finalizar wg sync.WaitGroup // Propriade mu sync.Mutex // Lista para armazenar inteiros list []int ) // Loop com mihão de indices for i := 0; i它们之间的区别是使用Uber库导入。
假设使用 2 个 CPU 运行基准测试时,结果是:
ns/op:提供执行特定操作所需时间的平均值(以纳秒为单位)。
请注意,我的 CPU 的可用总数为 8 个核心,这就是 runtime.NumCPU() 属性返回的值。然而,在运行基准测试时,我定义只使用两个 CPU,而没有使用 automaxprocs 的文件定义一次执行限制为 8 个 goroutine,而最高效的为 2 个,因为这样使用更少的分配可以使执行更加高效。
因此,我们的应用程序的可观察性和分析的重要性是显而易见的。
免责声明: 提供的所有资源部分来自互联网,如果有侵犯您的版权或其他权益,请说明详细缘由并提供版权或权益证明然后发到邮箱:[email protected] 我们会第一时间内为您处理。
Copyright© 2022 湘ICP备2022001581号-3