Einleitung: Warum die Überwachung von Rust-Anwendungen eine Herausforderung und Chance ist
Rust ist bekannt für seine Leistung und Speichersicherheit. Wenn Ihre Anwendung jedoch an den Grenzen der Hardware arbeitet (z. B. ein hochbelasteter WebSocket-Server oder ein Datenparser), reichen die Standardmetriken wie CPU und RAM oft nicht aus. Auf fortgeschrittener Ebene erfordert die Überwachung von Rust-Anwendungen nicht nur die Datenerfassung, sondern die Erstellung eines präzisen Alarmsystems, das einen Ausfall vorhersagt, bevor er eintritt.
In diesem Artikel betrachten wir, wie Sie ein produktionsreifes Überwachungssystem für Rust aufbauen: von benutzerdefinierten Metriken mit der prometheus-Crate bis zur Einrichtung von Alarmen über Alertmanager und Grafana.
1. Fortgeschrittene Metriken: Nicht nur HTTP-Anfragen
Die grundlegende Überwachung (Anzahl der Anfragen, Latenzen) ist das Minimum. Auf fortgeschrittener Ebene sammeln wir Metriken, die den inneren Zustand der Anwendung widerspiegeln: Warteschlangengrößen, Anzahl aktiver Tokio-Aufgaben, Zustand des Verbindungspools.
1.1. Benutzerdefinierte Metriken mit Histogram- und Gauge-Typen
Wir verwenden die Crate prometheus. Betrachten wir ein Beispiel für die Sammlung von Metriken für einen Datenbank-Verbindungspool:
use prometheus::{HistogramOpts, HistogramVec, Gauge, register_histogram_vec, register_gauge};use lazy_static::lazy_static;
lazy_static! { static ref DB_CONNECTION_POOL: Gauge = register_gauge!( "db_connection_pool_size", "Aktuelle Anzahl aktiver Verbindungen im Pool" ).unwrap();
static ref DB_QUERY_DURATION: HistogramVec = register_histogram_vec!( HistogramOpts::new("db_query_duration_seconds", "Ausführungszeit der Datenbankabfrage"), &["query_type", "status"] ).unwrap();}
// Verwendung im Codefn execute_query(query_type: &str) -> Result<(), String> { DB_CONNECTION_POOL.inc(); let start = std::time::Instant::now(); // ... Abfrage ausführen ... let duration = start.elapsed().as_secs_f64(); DB_QUERY_DURATION.with_label_values(&[query_type, "success"]).observe(duration); DB_CONNECTION_POOL.dec(); Ok(())}Wichtig: Verwenden Sie HistogramVec, um Abfragen nach Typ (SELECT, INSERT) und Status (success, error) zu kategorisieren. Dies ermöglicht die Erstellung von Heatmaps in Grafana.
1.2. Metriken aus dem Tokio-Runtime
Für asynchrone Anwendungen ist es entscheidend, die Auslastung des Schedulers zu kennen. Verwenden Sie tokio-metrics:
use tokio_metrics::RuntimeMetrics;use std::sync::Arc;
async fn monitor_runtime(metrics: Arc<RuntimeMetrics>) { loop { tokio::time::sleep(std::time::Duration::from_secs(5)).await; let num_alive_tasks = metrics.num_alive_tasks(); let blocking_threads = metrics.num_blocking_threads(); // Diese Werte über Gauge an Prometheus senden println!("Aktive Aufgaben: {}, Blockierende Threads: {}", num_alive_tasks, blocking_threads); }}Diese Metriken helfen, Task-Leaks zu erkennen (wenn num_alive_tasks ständig steigt) oder eine übermäßige Nutzung von block_in_place.
2. Architektur der Metrikenerfassung: Push vs Pull
Im Rust-Ökosystem wird häufiger das Pull-Modell (Prometheus Scraper) verwendet, aber für kurzlebige Prozesse (CLI-Tools, Parser) ist der Push-Ansatz über Pushgateway praktisch.
2.1. Pull-Modell: HTTP-Endpunkt
Die Standardmethode besteht darin, einen separaten HTTP-Server auf Port 9090 (oder einem anderen) innerhalb Ihrer Anwendung zu starten:
use warp::Filter;use prometheus::{Encoder, TextEncoder};
async fn metrics_handler() -> Result<impl warp::Reply, warp::Rejection> { let metric_families = prometheus::gather(); let mut buffer = Vec::new(); let encoder = TextEncoder::new(); encoder.encode(&metric_families, &mut buffer).unwrap(); Ok(warp::reply::with_header(bu