Heim  >  Artikel  >  Backend-Entwicklung  >  Golang: Wie Beobachtbarkeit und Profilerstellung eine nahezu nicht nachweisbare Drosselung aufdeckten

Golang: Wie Beobachtbarkeit und Profilerstellung eine nahezu nicht nachweisbare Drosselung aufdeckten

Susan Sarandon
Susan SarandonOriginal
2024-10-10 06:13:02488Durchsuche

In einem persönlichen Projekt mit Go, das Informationen über finanzielle Vermögenswerte von Bovespa erhält.
Das System nutzt intensiv die Parallelität und Parallelität mit Goroutinen und aktualisiert alle 8 Sekunden Asset-Informationen (zusammen mit Geschäftsberechnungen).
Anfangs erschienen keine Fehler oder Warnungen, aber ich bemerkte, dass die Ausführung einiger Goroutinen länger dauerte als andere.

Um genauer zu sein: Während die p99-Zeit 0,03 ms betrug, stieg sie an einigen Stellen auf 0,9 ms. Dies veranlasste mich, das Problem weiter zu untersuchen.

Ich habe festgestellt, dass ich einen Semaphor-Goroutine-Pool verwende, der auf der Grundlage der Variablen GOMAXPROCS erstellt wurde.
Mir wurde jedoch klar, dass es bei diesem Ansatz ein Problem gab.

Wenn wir die Variable GOMAXPROCS verwenden, erfasst sie die Anzahl der im Container verfügbaren Kerne nicht korrekt. Wenn der Container weniger verfügbare Kerne hat als die Gesamtzahl der VM, wird die Gesamtzahl der VM berücksichtigt. Beispielsweise verfügt meine VM über 8 verfügbare Kerne, der Container jedoch nur über 4. Dies führte dazu, dass 8 Goroutinen zur gleichzeitigen Ausführung erstellt wurden, was zu einer Drosselung führte.

Nach langer Recherche über Nacht habe ich eine von Uber entwickelte Bibliothek gefunden, die die Variable GOMAXPROCS automatisch effizienter anpasst, unabhängig davon, ob sie sich in einem Container befindet oder nicht. Diese Lösung erwies sich als äußerst stabil und effizient: automaxprocs

Golang: Como a observabilidade e profiling revelaram um throttling quase indetectável Über-gehen / automaxprocs

Stellen Sie GOMAXPROCS automatisch so ein, dass es dem CPU-Kontingent des Linux-Containers entspricht.

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

GOMAXPROCS automatisch so einstellen, dass es dem CPU-Kontingent des Linux-Containers entspricht.

Installation

go get -u go.uber.org/automaxprocs

Schnellstart

import _ "go.uber.org/automaxprocs"

func main() {
  // Your application logic here.
}
Vollbildmodus aufrufen Vollbildmodus verlassen

Leistung

Daten gemessen vom internen Load Balancer von Uber. Wir haben den Load Balancer mit 200 % CPU-Quote (d. h. 2 Kernen) ausgeführt:

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.

Das obige ist der detaillierte Inhalt vonGolang: Wie Beobachtbarkeit und Profilerstellung eine nahezu nicht nachweisbare Drosselung aufdeckten. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn