Maison  >  Article  >  développement back-end  >  Fuite de mémoire du serveur Web Gin Gonic ?

Fuite de mémoire du serveur Web Gin Gonic ?

PHPz
PHPzavant
2024-02-08 21:51:08628parcourir

Gin Gonic 网络服务器内存泄漏?

L'éditeur php Baicao a découvert qu'il peut y avoir un problème de fuite de mémoire lors de l'utilisation du serveur Web Gin Gonic. La fuite de mémoire est un bug courant qui amène le programme à occuper trop de ressources mémoire, affectant finalement la stabilité et les performances du système. Pour les développeurs, il est crucial de détecter et de résoudre rapidement les fuites de mémoire. Dans cet article, nous explorerons les causes et les solutions aux fuites de mémoire sur le serveur réseau Gin Gonic, aidant ainsi les développeurs à optimiser le code et à améliorer les performances du système.

Contenu de la question

Description

J'ai rencontré une fuite de mémoire potentielle sur le serveur Web gin-gonic/gin. J'ai essayé de reproduire le problème en créant un simple point de terminaison /health_check. Le point de terminaison /health_check est atteint toutes les secondes. Ce problème provoque une condition de manque de mémoire (MOO) lorsque la mémoire disponible du Pod est épuisée. Aucun autre conteneur n'est exécuté dans le pod.

J'ai également exposé les métriques pprof et prometheus pour comprendre le problème, mais je n'ai rien trouvé. Je ne vois aucun autre problème répertorié signalant le même problème, j'espère donc que quelqu'un pourra m'aider à isoler le problème.

Je ne vois aucune augmentation de la mémoire du tas ou de la pile, mais la mémoire du processus continue d'augmenter. Je peux voir l'augmentation en utilisant RSS et le bloc de mémoire correspondant en utilisant pmap, mais je ne peux pas comprendre pourquoi la mémoire n'est pas effacée ni à quoi sert la mémoire allouée.

Comment reproduire

Exemple de code simple avec des points de terminaison pertinents :

<code>package server

import (
    "fmt"
    "net/http"
    _ "net/http/pprof"

    "github.com/gin-gonic/gin"
    "github.com/prometheus/client_golang/prometheus/promhttp"
)

func setupRouter() *gin.Engine {
    router := gin.New()
    router.GET("/health_check", func(c *gin.Context) {
        c.String(http.StatusOK, "Hello World!")
    })
    router.GET("/debug/pprof", gin.WrapF(http.DefaultServeMux.ServeHTTP))
    router.GET("/debug/pprof/:pprofType", gin.WrapF(http.DefaultServeMux.ServeHTTP))
    router.GET("/metrics", func(c *gin.Context) {
        handler := promhttp.Handler()
        handler.ServeHTTP(c.Writer, c.Request)
    })

    return router
}

func Start() {
    router := setupRouter()
    err := router.Run(":8080")
    if err != nil {
        fmt.Printf("Error starting server: %v\n", err)
    }
}
</code>
<code>package main

import (
    "example.com/health-check/server"
)

func main() {
    server.Start()
}
</code>

Commande de construction :

go build -ldflags="-s -w" -race -o health-check main.go

Limite de ressources du pod :

  1. 64 Mo de RAM
  2. 1 processeur

Attente

Je m'attends à ce que l'utilisation de la mémoire reste cohérente. Certaines fluctuations sont acceptables, mais j'aimerais que l'utilisation de la mémoire reste globalement cohérente et ne manque pas de mémoire.

Résultats réels

Le pod est tombé en panne à cause d'un MOO qui a continué à augmenter la mémoire pendant environ 90 minutes. Chaque pic dans le graphique ci-dessous représente un redémarrage du pod en raison d'un MOO.

Coroutine

goroutine 8334 [running]:
runtime/pprof.writeGoroutineStacks({0x7f0e9e220b20, 0xc000506200})
    /usr/local/go/src/runtime/pprof/pprof.go:703 +0x75
runtime/pprof.writeGoroutine({0x7f0e9e220b20, 0xc000506200}, 0x2)
    /usr/local/go/src/runtime/pprof/pprof.go:692 +0x45
runtime/pprof.(*Profile).WriteTo(0x1509f00, {0x7f0e9e220b20, 0xc000506200}, 0xc?)
    /usr/local/go/src/runtime/pprof/pprof.go:329 +0x1b1
net/http/pprof.handler.ServeHTTP({0xc000051601, 0x9}, {0x7f0e5462fd18, 0xc000506200}, 0x0?)
    /usr/local/go/src/net/http/pprof/pprof.go:267 +0x58a
net/http/pprof.Index({0x7f0e5462fd18?, 0xc000506200}, 0xc000506600)
    /usr/local/go/src/net/http/pprof/pprof.go:384 +0x129
net/http.HandlerFunc.ServeHTTP(0xf53ac8, {0x7f0e5462fd18, 0xc000506200}, 0xc0000b0500?)
    /usr/local/go/src/net/http/server.go:2136 +0x48
net/http.(*ServeMux).ServeHTTP(0xc0004a3740?, {0x7f0e5462fd18, 0xc000506200}, 0xc000506600)
    /usr/local/go/src/net/http/server.go:2514 +0xbd
example.com/health-check/server.setupRouter.WrapF.func4(0xc000506200)
    /go/pkg/mod/github.com/gin-gonic/[email&#160;protected]/utils.go:42 +0x97
github.com/gin-gonic/gin.(*Context).Next(...)
    /go/pkg/mod/github.com/gin-gonic/[email&#160;protected]/context.go:174
github.com/gin-gonic/gin.(*Engine).handleHTTPRequest(0xc0001871e0, 0xc000506200)
    /go/pkg/mod/github.com/gin-gonic/[email&#160;protected]/gin.go:620 +0xb91
github.com/gin-gonic/gin.(*Engine).ServeHTTP(0xc0001871e0, {0x105b2e0?, 0xc00027e540}, 0xc000506600)
    /go/pkg/mod/github.com/gin-gonic/[email&#160;protected]/gin.go:576 +0x425
net/http.serverHandler.ServeHTTP({0xc000600db0?}, {0x105b2e0, 0xc00027e540}, 0xc000506600)
    /usr/local/go/src/net/http/server.go:2938 +0x2a2
net/http.(*conn).serve(0xc000240900, {0x105c478, 0xc0001ad5c0})
    /usr/local/go/src/net/http/server.go:2009 +0xc25
created by net/http.(*Server).Serve in goroutine 1
    /usr/local/go/src/net/http/server.go:3086 +0x80d

goroutine 1 [IO wait]:
internal/poll.runtime_pollWait(0x7f0e9ea9d410, 0x72)
    /usr/local/go/src/runtime/netpoll.go:343 +0x85
internal/poll.(*pollDesc).wait(0xc0003a62a0, 0x4ae001?, 0x0)
    /usr/local/go/src/internal/poll/fd_poll_runtime.go:84 +0xb1
internal/poll.(*pollDesc).waitRead(...)
    /usr/local/go/src/internal/poll/fd_poll_runtime.go:89
internal/poll.(*FD).Accept(0xc0003a6280)
    /usr/local/go/src/internal/poll/fd_unix.go:611 +0x405
net.(*netFD).accept(0xc0003a6280)
    /usr/local/go/src/net/fd_unix.go:172 +0x3e
net.(*TCPListener).accept(0xc00024d480)
    /usr/local/go/src/net/tcpsock_posix.go:152 +0x3e
net.(*TCPListener).Accept(0xc00024d480)
    /usr/local/go/src/net/tcpsock.go:315 +0x65
net/http.(*Server).Serve(0xc00054c000, {0x105b520, 0xc00024d480})
    /usr/local/go/src/net/http/server.go:3056 +0x57f
net/http.(*Server).ListenAndServe(0xc00054c000)
    /usr/local/go/src/net/http/server.go:2985 +0xbd
net/http.ListenAndServe(...)
    /usr/local/go/src/net/http/server.go:3239
github.com/gin-gonic/gin.(*Engine).Run(0xc0001871e0, {0xc0003bbef8, 0x1, 0x1})
    /go/pkg/mod/github.com/gin-gonic/[email&#160;protected]/gin.go:386 +0x38d
example.com/health-check/server.Start()
    /app/server/server.go:49 +0x52
main.main()
    /app/main.go:8 +0x1d

goroutine 82 [IO wait]:
internal/poll.runtime_pollWait(0x7f0e9ea9d318, 0x72)
    /usr/local/go/src/runtime/netpoll.go:343 +0x85
internal/poll.(*pollDesc).wait(0xc0002c60a0, 0xc000568000?, 0x0)
    /usr/local/go/src/internal/poll/fd_poll_runtime.go:84 +0xb1
internal/poll.(*pollDesc).waitRead(...)
    /usr/local/go/src/internal/poll/fd_poll_runtime.go:89
internal/poll.(*FD).Read(0xc0002c6080, {0xc000568000, 0x1000, 0x1000})
    /usr/local/go/src/internal/poll/fd_unix.go:164 +0x3e5
net.(*netFD).Read(0xc0002c6080, {0xc000568000, 0x1000, 0x1000})
    /usr/local/go/src/net/fd_posix.go:55 +0x4b
net.(*conn).Read(0xc000514010, {0xc000568000, 0x1000, 0x1000})
    /usr/local/go/src/net/net.go:179 +0xad
net/http.(*connReader).Read(0xc0002c4450, {0xc000568000, 0x1000, 0x1000})
    /usr/local/go/src/net/http/server.go:791 +0x2b5
bufio.(*Reader).fill(0xc000536d20)
    /usr/local/go/src/bufio/bufio.go:113 +0x29a
bufio.(*Reader).Peek(0xc000536d20, 0x4)
    /usr/local/go/src/bufio/bufio.go:151 +0xc7
net/http.(*conn).serve(0xc0002e21b0, {0x105c478, 0xc0001ad5c0})
    /usr/local/go/src/net/http/server.go:2044 +0xe7c
created by net/http.(*Server).Serve in goroutine 1
    /usr/local/go/src/net/http/server.go:3086 +0x80d

goroutine 8335 [IO wait]:
internal/poll.runtime_pollWait(0x7f0e9ea9d128, 0x72)
    /usr/local/go/src/runtime/netpoll.go:343 +0x85
internal/poll.(*pollDesc).wait(0xc0002c62a0, 0xc000600dc1?, 0x0)
    /usr/local/go/src/internal/poll/fd_poll_runtime.go:84 +0xb1
internal/poll.(*pollDesc).waitRead(...)
    /usr/local/go/src/internal/poll/fd_poll_runtime.go:89
internal/poll.(*FD).Read(0xc0002c6280, {0xc000600dc1, 0x1, 0x1})
    /usr/local/go/src/internal/poll/fd_unix.go:164 +0x3e5
net.(*netFD).Read(0xc0002c6280, {0xc000600dc1, 0x1, 0x1})
    /usr/local/go/src/net/fd_posix.go:55 +0x4b
net.(*conn).Read(0xc0000b04d0, {0xc000600dc1, 0x1, 0x1})
    /usr/local/go/src/net/net.go:179 +0xad
net/http.(*connReader).backgroundRead(0xc000600db0)
    /usr/local/go/src/net/http/server.go:683 +0x83
created by net/http.(*connReader).startBackgroundRead in goroutine 8334
    /usr/local/go/src/net/http/server.go:679 +0x246

Environnement

  • Version Go : go1.21.1 Linux/amd64
  • image docker : 1.21.1-bullseye
  • version gin (ou référence de commit) : github.com/gin-gonic/gin v1.9.1
  • Système d'exploitation : Debian GNU/Linux 11 (Bulls Eye)
  • GOGC=10;GOMEMLIMIT=64MiB (J'ai également essayé d'utiliser la valeur par défaut, mais même résultat)

Merci pour toute aide ou conseil, très apprécié.

Solution

J'ai donc fait quelques expériences pour savoir où la mémoire augmente.

J'ai essayé de tester sans l'indicateur de build -race et la mémoire semble bonne maintenant. Je ne le vois pas augmenter de manière constante sur les serveurs inactifs (seules les sondes d'activité, de préparation et les points de terminaison de métriques sont disponibles).

Je ne sais pas vraiment pourquoi cela se produit, ni si cela est attendu, mais je vais y réfléchir plus en profondeur et j'ai temporairement supprimé le drapeau de nos déploiements de production.

Partage de la tendance de la mémoire après avoir supprimé cet indicateur (la commande build est go build), le pic est un test de charge que j'exécute, déclenchant 1 million de requêtes (100 en parallèle à la fois) :

Voici l'utilisation de la mémoire du Pod :

Ce qui suit est RSS :

Voici l'utilisation de la mémoire du tas et de la pile :

PS : je marque ceci comme réponse pour l'instant, mais n'hésitez pas à me corriger, je suis encore nouveau sur Golang et merci beaucoup pour les contributions de chacun. Si je trouve quelque chose de contraire à ce résultat, je mettrai à jour/supprimerai cette réponse, merci.

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:
Cet article est reproduit dans:. en cas de violation, veuillez contacter admin@php.cn Supprimer