Ich war immer verwirrt über das wachsende Wohngedächtnis meiner Go-Anwendungen und musste schließlich die Profiling-Tools lernen, die im Go-Ökosystem vorhanden sind. Runtime bietet viele Metriken innerhalb einer Laufzeit.Memstats- Struktur, aber es ist möglicherweise schwer zu verstehen, welche davon dazu beitragen können, die Gründe für das Speicherwachstum herauszufinden. Daher sind einige zusätzliche Tools erforderlich.
Profiling-Umgebung
Verwenden in Ihrer Anwendung https://github.com/tevjef/go-runtime-metrics . Zum Beispiel können Sie dies in Ihre main
:
import(
metrics "github.com/tevjef/go-runtime-metrics"
)
func main() {
metrics.DefaultConfig.CollectionInterval = time.Second
if err := metrics.RunCollector(metrics.DefaultConfig); err != nil {
}
}
Lauf InfluxDB
und Grafana
in Docker
Containern:
docker run --name influxdb -d -p 8086:8086 influxdb
docker run -d -p 9090:3000/tcp --link influxdb --name=grafana grafana/grafana:4.1.0
Richten Sie die Interaktion zwischen Grafana
und einInfluxDB
Grafana
Richten (Grafana-Hauptseite -> Obere linke Ecke -> Datenquellen -> Neue Datenquelle hinzufügen):
Importieren Sie das Dashboard Nr. 3242 von https://grafana.com (Grafana-Hauptseite -> obere linke Ecke -> Dashboard -> Importieren):
Starten Sie abschließend Ihre Anwendung: Sie überträgt Laufzeitmetriken an die Konkurrenten Influxdb
. Setzen Sie Ihre Anwendung einer angemessenen Belastung aus (in meinem Fall war sie recht klein - 5 RPS für mehrere Stunden).
Analyse des Speicherverbrauchs
Sys
(das Synonim von RSS
) Kurve ist Kurve ziemlich ähnlich HeapSys
. Es stellt sich heraus, dass die dynamische Speicherzuweisung der Hauptfaktor für das gesamte Speicherwachstum war, sodass die geringe Menge an Speicher, die von Stapelvariablen verbraucht wird, konstant zu sein scheint und ignoriert werden kann.
- Die konstante Menge an Goroutinen garantiert das Fehlen von Goroutinenlecks / Stapelvariablenlecks;
- Die Gesamtmenge der zugewiesenen Objekte bleibt während der Lebensdauer des Prozesses gleich (es macht keinen Sinn, die Schwankungen zu berücksichtigen).
- Die überraschendste Tatsache:
HeapIdle
wächst mit der gleichen Geschwindigkeit wie a Sys
, während HeapReleased
immer Null ist. Offensichtlich Laufzeit nicht zurück Speicher OS überhaupt , zumindest unter den Bedingungen dieses Tests:
HeapIdle minus HeapReleased estimates the amount of memory
that could be returned to the OS, but is being retained by
the runtime so it can grow the heap without requesting more
memory from the OS.
Für diejenigen, die versuchen, das Problem des Speicherverbrauchs zu untersuchen, würde ich empfehlen, die beschriebenen Schritte zu befolgen, um einige triviale Fehler (wie Goroutine-Leck) auszuschließen.
Speicher explizit freigeben
Es ist interessant, dass man den Speicherverbrauch durch explizite Aufrufe an debug.FreeOSMemory()
:
func init() {
go func() {
t := time.Tick(time.Second)
for {
<-t
debug.FreeOSMemory()
}
}()
}
Tatsächlich sparte dieser Ansatz im Vergleich zu den Standardbedingungen etwa 35% des Speichers.