首頁 > 後端開發 > Golang > 主體

Golang:可觀察性和分析如何揭示幾乎無法偵測到的節流

Susan Sarandon
發布: 2024-10-10 06:13:02
原創
487 人瀏覽過

Dalam projek peribadi dengan Go, yang memperoleh maklumat tentang aset kewangan daripada Bovespa.
Sistem ini menggunakan penggunaan konkurensi dan selari dengan goroutin, mengemas kini maklumat aset (bersama-sama dengan pengiraan perniagaan) setiap 8 saat.
Pada mulanya, tiada ralat atau amaran muncul, tetapi saya mendapati bahawa sesetengah gorout mengambil masa yang lebih lama daripada yang lain untuk dilaksanakan.

Untuk lebih spesifik, manakala masa p99 ialah 0.03 ms, pada beberapa titik, ia meningkat kepada 0.9 ms. Ini membawa saya untuk menyiasat masalah ini dengan lebih lanjut.

Saya mendapati bahawa saya menggunakan kolam goroutine semaphore, yang dicipta berdasarkan pembolehubah GOMAXPROCS.
Walau bagaimanapun, saya menyedari terdapat masalah dengan pendekatan ini.

Apabila kami menggunakan pembolehubah GOMAXPROCS, ia tidak menangkap bilangan teras yang tersedia dalam bekas dengan betul. Jika bekas mempunyai kurang teras yang tersedia daripada jumlah VM, ia mengambil kira jumlah VM. Sebagai contoh, VM saya mempunyai 8 teras yang tersedia, tetapi bekas itu hanya mempunyai 4. Ini menghasilkan 8 goroutin untuk dijalankan pada masa yang sama, menyebabkan pendikitan.

Selepas banyak penyelidikan semalaman, saya menemui perpustakaan yang dibangunkan oleh Uber yang melaraskan pembolehubah GOMAXPROCS secara automatik dengan lebih cekap, tidak kira sama ada ia berada di dalam bekas atau tidak. Penyelesaian ini terbukti sangat stabil dan cekap: automaxprocs

Golang: Como a observabilidade e profiling revelaram um throttling quase indetectável uber-go / automaxprocs

Tetapkan GOMAXPROCS secara automatik untuk memadankan kuota CPU kontena Linux.

automaxprocs Golang: Como a observabilidade e profiling revelaram um throttling quase indetectável Golang: Como a observabilidade e profiling revelaram um throttling quase indetectável Golang: Como a observabilidade e profiling revelaram um throttling quase indetectável

Tetapkan GOMAXPROCS secara automatik untuk memadankan kuota CPU kontena Linux.

Pemasangan

pergi dapatkan -u go.uber.org/automaxprocs

Mula Pantas

import _ "go.uber.org/automaxprocs"

func main() {
  // Your application logic here.
}
登入後複製
Masukkan mod skrin penuh Keluar daripada mod skrin penuh

Prestasi

Data diukur daripada pengimbang beban dalaman Uber. Kami menjalankan pengimbang beban dengan 200% kuota CPU (iaitu, 2 teras):

GOMAXPROCS RPS P50 (ms) P99.9 (ms)
1 28,893.18 1.46 19.70
2 (equal to quota) 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
Default (24) 22,191.40 0.45 76.19

When GOMAXPROCS is increased above the CPU quota, we see P50 decrease slightly, but see significant increases to P99. We also see that the total RPS handled also decreases.

When GOMAXPROCS is higher than the CPU quota allocated, we also saw significant throttling:

$ cat /sys/fs/cgroup/cpu,cpuacct/system.slice/[...]/cpu.stat
nr_periods 42227334
nr_throttled 131923
throttled_time 88613212216618
登入後複製

Once GOMAXPROCS was reduced to match the CPU quota, we saw no CPU throttling.

View on GitHub
.

Após implementar o uso dessa biblioteca, o problema foi resolvido, e agora o tempo p99 se manteve em 0.02 ms constantemente. Essa experiência destacou a importância da observabilidade e do profiling em sistemas concorrentes.

A seguir um exemplo bem simples, mas que consegue demonstrar a diferença de desempenho.

Utilizando o pacote nativo de testes e benckmak do Go, criei dois arquivos:

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; i < 1000000; i++ {
        semaphore <- struct{}{}
        // Adiciona ao waitGroup que existe mais uma goroutine para ser executada
        wg.Add(1)

        // Atribui a função a uma nova goroutine
        go func(i int) {
            // Quando a função finalizar, informa o semáforo e finaliza um registro do waitGroup
            defer func() {
                <-semaphore
                wg.Done()
            }()
            // Faz o bloqueio do array para outra goroutine não sobreescrever
            mu.Lock()
            // Adiciona o indice, em mais uma posição no array
            list = append(list, i)
            // Desbloqueia o array
            mu.Unlock()
        }(i)
    }
}

登入後複製

benchmarking_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 < 1000000; i++ {
        semaphore <- struct{}{}
        // Adiciona ao waitGroup que existe mais uma goroutine para ser executada
        wg.Add(1)

        // Atribui a função a uma nova goroutine
        go func(i int) {
            // Quando a função finalizar, informa o semáforo e finaliza um registro do waitGroup
            defer func() {
                <-semaphore
                wg.Done()
            }()
            // Faz o bloqueio do array para outra goroutine não sobreescrever
            mu.Lock()
            // Adiciona o indice, em mais uma posição no array
            list = append(list, i)
            // Desbloqueia o array
            mu.Unlock()
        }(i)
    }
}

登入後複製

A diferença entra elas, é que uma esta com a importação de biblioteca da Uber.

Ao executar o benchmark passando que seriam usados 2 CPUs, o resultado foi:

Golang: Como a observabilidade e profiling revelaram um throttling quase indetectável

ns/op: fornece uma média em nanosegundos de quanto tempo leva para executar uma operação específica.

Percebam, que o total disponível da minha CPU são 8 núcleos, e foi o que a propriedade runtime.NumCPU() retornou. Porém, como na execução do benchmark, defini que o uso seriam de apenas duas CPUs, a o arquivo que não utilizou a automaxprocs, definiu que o limite de execução por vez, seriam de 8 goroutines, enquanto o mais eficiente seriam 2, pois dessa maneira se usa menos alocação deixa mais eficiente a execução.

Então, fica nítido a importância de observabilidade e proffiling das nossas aplicações.

以上是Golang:可觀察性和分析如何揭示幾乎無法偵測到的節流的詳細內容。更多資訊請關注PHP中文網其他相關文章!

來源:dev.to
上一篇:Kubernetes Operator 如何處理並發? 下一篇:Golang:規劃如何在高使用率應用程式中顯示日誌的重要性
本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
作者最新文章
最新問題
關於CSS心智圖的課件在哪? 課件
來自於 2024-04-16 10:10:18
0
0
1644
相關專題
更多>
熱門推薦
熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板