Speicherleck des Gin Gonic-Webservers?

PHPz
Freigeben: 2024-02-08 21:51:08
nach vorne
626 Leute haben es durchsucht

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

php-Editor Baicao hat festgestellt, dass bei der Verwendung des Gin Gonic-Webservers möglicherweise ein Speicherverlustproblem vorliegt. Speicherverlust ist ein häufiger Fehler, der dazu führt, dass das Programm zu viele Speicherressourcen belegt, was letztendlich die Stabilität und Leistung des Systems beeinträchtigt. Für Entwickler ist es von entscheidender Bedeutung, Speicherlecks rechtzeitig zu erkennen und zu beheben. In diesem Artikel untersuchen wir die Ursachen und Lösungen von Speicherlecks im Gin Gonic-Netzwerkserver und helfen Entwicklern, Code zu optimieren und die Systemleistung zu verbessern.

Frageninhalt

Beschreibung

Ich habe einen potenziellen Speicherverlust im Gin-Gonic/Gin-Webserver festgestellt. Ich habe versucht, das Problem zu reproduzieren, indem ich einen einfachen /health_check-Endpunkt erstellt habe. Der Endpunkt /health_check wird jede Sekunde getroffen. Dieses Problem führt zu einem Out-of-Memory-Zustand (OOM), wenn der verfügbare Pod-Speicher erschöpft ist. Im Pod laufen keine anderen Container.

Ich habe auch pprof- und prometheus-Metriken bereitgestellt, um das Problem zu verstehen, konnte aber nichts finden. Ich sehe keine anderen aufgelisteten Probleme, die das gleiche Problem melden, daher hoffe ich, dass mir jemand helfen kann, das Problem einzugrenzen.

Ich sehe keinen Anstieg des Heap- oder Stack-Speichers, aber der Prozessspeicher nimmt weiter zu. Ich kann den Anstieg mit RSS und den entsprechenden Speicherblock mit pmap sehen, kann aber nicht nachvollziehen, warum der Speicher nicht gelöscht wird oder wofür der zugewiesene Speicher verwendet wird.

Wie man reproduziert

Einfaches Codebeispiel mit relevanten Endpunkten:

<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>
Nach dem Login kopieren
<code>package main

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

func main() {
    server.Start()
}
</code>
Nach dem Login kopieren

Build-Befehl:

go build -ldflags="-s -w" -race -o health-check main.go
Nach dem Login kopieren

Pod-Ressourcenlimit:

  1. 64 MB RAM
  2. 1 CPU

Erwartung

Ich gehe davon aus, dass die Speichernutzung konstant bleibt. Einige Schwankungen sind akzeptabel, aber ich möchte, dass die Speichernutzung weitgehend konstant bleibt und nicht der Speicher knapp wird.

Tatsächliche Ergebnisse

Der Pod stürzte aufgrund von OOM ab, wodurch der Speicher etwa 90 Minuten lang weiter anstieg. Jeder Anstieg in der Grafik unten stellt einen Pod-Neustart aufgrund von OOM dar.

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
Nach dem Login kopieren

Umwelt

  • Go-Version: go1.21.1 linux/amd64
  • Docker-Image: 1.21.1-Bullseye
  • gin-Version (oder Commit-Referenz): github.com/gin-gonic/gin v1.9.1
  • Betriebssystem: Debian GNU/Linux 11 (Bulls Eye)
  • GOGC=10;GOMEMLIMIT=64MiB (Auch mit Standard versucht, aber das gleiche Ergebnis)

Vielen Dank für jede Hilfe oder Anleitung, sehr geschätzt.

Lösung

Also habe ich einige Experimente durchgeführt, um herauszufinden, wo das Gedächtnis zunimmt.

Ich habe versucht, ohne das -race Build-Flag zu testen, und der Speicher scheint jetzt in Ordnung zu sein. Ich sehe keinen stetigen Anstieg auf inaktiven Servern (nur Liveness-, Bereitschaftstests und Metrik-Endpunkte sind verfügbar).

Ich bin mir nicht ganz sicher, warum dies geschieht oder ob dies zu erwarten ist, aber ich werde es genauer untersuchen und habe die Flagge vorübergehend aus unseren Produktionsbereitstellungen entfernt.

Der Speichertrend nach dem Entfernen dieses Flags (Build-Befehl ist go build) wird geteilt. Der Höhepunkt ist ein von mir ausgeführter Auslastungstest, der 1 Mio. Anforderungen auslöst (jeweils 100 gleichzeitig):

Das Folgende ist die Pod-Speichernutzung:

Das Folgende ist RSS:

Hier sind die Heap- und Stack-Speichernutzung:

PS: Ich markiere dies vorerst als Antwort, aber Sie können mich gerne korrigieren, ich bin noch neu bei Golang und vielen Dank für alle Beiträge. Wenn ich etwas finde, das diesem Ergebnis widerspricht, werde ich diese Antwort aktualisieren/löschen, vielen Dank.

Das obige ist der detaillierte Inhalt vonSpeicherleck des Gin Gonic-Webservers?. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Quelle:stackoverflow.com
Erklärung dieser Website
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
Beliebte Tutorials
Mehr>
Neueste Downloads
Mehr>
Web-Effekte
Quellcode der Website
Website-Materialien
Frontend-Vorlage