Maison > développement back-end > Golang > le corps du texte

Golang : comment l'observabilité et le profilage ont révélé une limitation presque indétectable

Susan Sarandon
Libérer: 2024-10-10 06:13:02
original
486 Les gens l'ont consulté

Dans un projet personnel avec Go, qui obtient des informations sur les actifs financiers auprès de Bovespa.
Le système utilise intensivement la concurrence et le parallélisme avec les goroutines, mettant à jour les informations sur les actifs (ainsi que les calculs commerciaux) toutes les 8 secondes.
Au départ, aucune erreur ni avertissement n'est apparu, mais j'ai remarqué que certaines goroutines prenaient plus de temps que d'autres à s'exécuter.

Pour être plus précis, alors que le temps p99 était de 0,03 ms, à certains moments, il est passé à 0,9 ms. Cela m'a amené à approfondir le problème.

J'ai découvert que j'utilisais un pool de goroutines de sémaphore, créé sur la base de la variable GOMAXPROCS.
Cependant, j'ai réalisé qu'il y avait un problème avec cette approche.

Lorsque nous utilisons la variable GOMAXPROCS, elle ne capture pas correctement le nombre de cœurs disponibles dans le conteneur. Si le conteneur a moins de cœurs disponibles que le total de la VM, il prend en compte le total de la VM. Par exemple, ma VM a 8 cœurs disponibles, mais le conteneur n'en avait que 4. Cela a entraîné la création de 8 goroutines à exécuter en même temps, provoquant une limitation.

Après de nombreuses recherches du jour au lendemain, j'ai trouvé une bibliothèque développée par Uber qui ajuste automatiquement la variable GOMAXPROCS plus efficacement, qu'elle soit dans un conteneur ou non. Cette solution s'est avérée extrêmement stable et efficace : automaxprocs

Golang: Como a observabilidade e profiling revelaram um throttling quase indetectável uber-aller / procédures automatiques maximales

Définissez automatiquement GOMAXPROCS pour qu'il corresponde au quota de CPU du conteneur 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

Définissez automatiquement GOMAXPROCS pour qu'il corresponde au quota de CPU du conteneur Linux.

Installation

allez chercher -u go.uber.org/automaxprocs

Démarrage rapide

import _ "go.uber.org/automaxprocs"

func main() {
  // Your application logic here.
}
Copier après la connexion
Entrez en mode plein écran Quitter le mode plein écran

Performances

Données mesurées à partir de l'équilibreur de charge interne d'Uber. Nous avons exécuté l'équilibreur de charge avec un quota de CPU de 200 % (c'est-à-dire 2 cœurs) :

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
Copier après la connexion

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)
    }
}

Copier après la connexion

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)
    }
}

Copier après la connexion

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.

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

source:dev.to
Article précédent:Comment les opérateurs Kubernetes gèrent-ils la concurrence ? Article suivant:Golang : importance de planifier l'affichage des journaux dans les applications à forte utilisation
Déclaration de ce site Web
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn
Derniers articles par auteur
Derniers numéros
Rubriques connexes
Plus>
Recommandations populaires
Tutoriels populaires
Plus>
Derniers téléchargements
Plus>
effets Web
Code source du site Web
Matériel du site Web
Modèle frontal