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.
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.
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 :
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.
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 protected]/utils.go:42 +0x97 github.com/gin-gonic/gin.(*Context).Next(...) /go/pkg/mod/github.com/gin-gonic/[email protected]/context.go:174 github.com/gin-gonic/gin.(*Engine).handleHTTPRequest(0xc0001871e0, 0xc000506200) /go/pkg/mod/github.com/gin-gonic/[email protected]/gin.go:620 +0xb91 github.com/gin-gonic/gin.(*Engine).ServeHTTP(0xc0001871e0, {0x105b2e0?, 0xc00027e540}, 0xc000506600) /go/pkg/mod/github.com/gin-gonic/[email 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 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
Merci pour toute aide ou conseil, très apprécié.
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 :
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!