Maison  >  Article  >  développement back-end  >  Utiliser le modèle Heartbeats dans Golang

Utiliser le modèle Heartbeats dans Golang

Mary-Kate Olsen
Mary-Kate Olsenoriginal
2024-11-03 06:08:30926parcourir

Utilizando o pattern Heartbeats em Golang

Implémentation de Heartbeats dans Go pour la surveillance des applications

Au cours de mes aventures en équilibrage Data & Software Engineer, je cherche toujours quelque chose d'un peu différent dans GoLang pour étudier, comprendre son fonctionnement et l'appliquer à des choses plus complexes que certains cours et articles traditionnels de base que je trouve sur Internet. . Dans ce court article, je vais rapporter et démontrer comment j'ai implémenté via Go Routines, le package time utilisant Ticker pour simuler le battement de coeur ("Je suis vivant") de l'application, en plus de l'utilisation des canaux, etc.

Ce n'est pas une nouveauté pour beaucoup qu'il soit extrêmement important de s'assurer que quiconque appelle une certaine fonction sache si la fonction prend du temps, est en cours de traitement ou est verrouillée. Cela dit, plusieurs autres terminologies ont émergé telles que Trace, Metrics, connectivité, etc., qui ont été introduites dans les applications de surveillance qui utilisent dans la plupart des cas des agents installés sur les serveurs d'applications qui collectent les métriques et les envoient à des interfaces qui visualisent toutes (ou presque) le statut de votre candidature. Parmi ces outils nous avons DataDog, NewRelic, Slack, Grafana, Jaeger, etc.

Qu'aurons-nous ici ?

Alors que j'étudie et réfléchis à la création de quelque chose de rapide et simple qui aborde des concepts Go plus avancés, j'ai créé une application relativement simple qui utilise le modèle de battements de cœur. Celui qui m'appelle reçoit le résultat et, en même temps, l'information si je suis toujours actif ou non. Dans un scénario plus avancé, cela peut être intéressant pour personnaliser ce qui est réellement une application active en fonction de certaines particularités métier, puisqu'une simple implémentation d'un Prometheus résout ce cas (l'application est-elle active ? CPU, Mémoire, goroutines ouvertes), mais pas avec des commentaires simultanés et personnalisables.

Heure de Code !

En termes de structure, je n'ai créé que trois fichiers au sein de mon package avec go mod :

  • Dictionary.go : contient un dictionnaire de noms pour la fonction à rechercher.
  • task.go : Tâche qui contient la fonction d'analyser les noms du dictionnaire et, en même temps, d'informer s'il est actif ou non via le rythme de la chaîne de team.Ticker.
  • task_test.go : effectue un test unitaire de la fonction présente dans task.go pour voir à la fois la réponse des données du dictionnaire et également des informations indiquant si l'application est toujours active !

dictionnaire.go

Cette partie du code Go définit une variable appelée « dictionnaire » qui est une carte qui associe des caractères de type rune à des chaînes.

Chaque entrée de carte est une clé (rune) et une valeur (chaîne). Dans l'exemple ci-dessous, les touches sont des lettres minuscules de l'alphabet et les valeurs sont des noms associés à chaque lettre. Par exemple, la lettre « a » est associée au nom « airton », la lettre « b » est associée au nom « bruno », et ainsi de suite :

package heartbeat

var dicionario = map[rune]string{
    'a': "airton",
    'b': "bruno",
    'c': "carlos",
    'd': "daniel",
    'e': "eduardo",
    'f': "felipe",
    'g': "gustavo",
}

tâche.go

J'explique mieux ci-dessous après le code complet chaque partie du code :

package heartbeat

import (
    "context"
    "fmt"
    "time"
)

func ProcessingTask(
    ctx context.Context, letras chan rune, interval time.Duration,
) (<-chan struct{}, <-chan string) {

    heartbeats := make(chan struct{}, 1)
    names := make(chan string)

    go func() {
        defer close(heartbeats)
        defer close(names)

        beat := time.NewTicker(interval)
        defer beat.Stop()

        for letra := range letras {
            select {
            case <-ctx.Done():
                return
            case <-beat.C:
                select {
                case heartbeats <- struct{}{}:
                default:
                }
            case names <- dicionario[letra]:
                lether := dicionario[letra]
                fmt.Printf("Letra: %s \n", lether)

                time.Sleep(3 * time.Second) // Simula um tempo de espera para vermos o hearbeats
            }
        }
    }()

    return heartbeats, names
}

Importer des dépendances

package heartbeat

import (
    "context"
    "fmt"
    "time"
)

Ici, j'ai mon package Heartbeat qui sera chargé d'implémenter une fonctionnalité qui envoie des « battements de cœur » à un intervalle de temps spécifique, lors du traitement des tâches. Pour cela, j'ai besoin de contexte (Context Management), de fmt (pour le formatage des chaînes) et de time pour le contrôle du temps.

Définition de la fonction initiale

func ProcessingTask (
    ctx context.Context, letras chan rune, interval time.Duration,
) (<-chan struct{}, <-chan string) {

C'est la définition de la fonction ProcessingTask qui prend un contexte ctx, un canal de lettres (un canal qui reçoit des caractères Unicode) et un intervalle de temps comme arguments. La fonction renvoie deux canaux : un canal de battements de cœur qui envoie une structure vide pour chaque « battement de cœur » et un canal de noms qui envoie le nom de la lettre correspondant à chaque caractère reçu.

Canaux

heartbeats := make(chan struct{}, 1)
names := make(chan string)

Ces deux lignes créent deux canaux : heartbeats est un canal tampon d'une capacité d'un élément et names est un canal sans tampon.

Go Routine qui fait le gros du travail

go func() 
    defer close(heartbeats)
    defer close(names)

    beat := time.NewTicker(interval)
    defer beat.Stop()

    for letra := range letras {
        select {
        case <-ctx.Done():
            return
        case <-beat.C:
            select {
            case heartbeats <- struct{}{}:
            default:
            }
        case names <- dicionario[letra]:
            lether := dicionario[letra]
            fmt.Printf("Letra: %s \n", lether)

            time.Sleep(3 * time.Second) // Simula um tempo de espera para vermos o hearbeats
        }
    }
}()

return heartbeats, names

Il s'agit d'une goroutine anonyme (ou fonction anonyme qui s'exécute dans un nouveau thread) qui exécute la logique principale de la fonction ProcessingTask. Il utilise une boucle for-range pour lire les caractères du canal des lettres. Dans la boucle, utilisez une sélection pour choisir une action à effectuer parmi les options disponibles :

  • case <-ctx.Done() : Si le contexte est annulé, la fonction se termine immédiatement, en utilisant l'instruction return.
  • case <-beat.C : Si le ticker de battement envoie une valeur, la goroutine essaie d'envoyer une structure vide au canal des battements de cœur en utilisant une sélection avec une valeur par défaut vide.
  • noms de cas <- dictionnaire[lettre] : si une lettre est reçue, la goroutine obtient le nom de la lettre correspondante à partir du dictionnaire dictionnaire, l'envoie au canal des noms, imprime la lettre à l'écran à l'aide du package fmt et attend trois secondes avant de passer au caractère suivant. Cette attente simulée permet de voir les « battements de cœur » envoyés.

Enfin, la fonction renvoie les battements de cœur et nomme les canaux.

Tester l'application

task_test.go

package heartbeat

var dicionario = map[rune]string{
    'a': "airton",
    'b': "bruno",
    'c': "carlos",
    'd': "daniel",
    'e': "eduardo",
    'f': "felipe",
    'g': "gustavo",
}

Ici, j'ai créé un test unitaire Go pour la fonction ProcessingTask qui a été expliquée précédemment. La fonction de test TestProcessingTask crée un contexte avec un délai d'attente de 20 secondes et un canal de caractères Unicode (lettres). La goroutine anonyme envoie ensuite les paroles au canal des paroles. La fonction ProcessingTask est ensuite appelée avec le contexte, le canal de caractères Unicode et un intervalle de temps. Il renvoie deux canaux, un canal de battement de cœur et un canal de mots.

La fonction de test exécute ensuite une boucle infinie avec une sélection, qui lit à partir de trois canaux : le contexte, le canal de battement de cœur et le canal de mots.

Si le contexte est annulé, la boucle de test est terminée. Si un battement de cœur est reçu, un message « Application Up ! » est imprimé sur une sortie standard. Si un mot est reçu, le test vérifie si le mot est présent dans le dictionnaire de lettres. S'il n'est pas présent, le test échoue et un message d'erreur s'affiche.

Par conséquent, ce test unitaire teste notre fonction ProcessingTask, qui reçoit des caractères d'un canal, envoie des noms de lettres à un autre canal et émet les « battements de cœur » lors de son exécution dans un contexte dans lequel j'ai utilisé une limite de temps. Ahhh... et il vérifie aussi si les noms des lettres envoyées au canal de mots sont présents dans le dictionnaire.

Mes conclusions

Ce code Go illustre quelques concepts importants du langage Go et des tests unitaires :

  • Contexte
  • Goroutines
  • Chaînes
  • Tests unitaires (utilisation de la sélection pour surveiller plusieurs canaux)

Projet complet sur mon GitHub : https://github.com/AirtonLira/heartbeatsGolang

LinkedIn - Airton Lira Junior

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!

Déclaration:
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