Zum Inhalt springen
Deutsch

Failure Detector

Ein geclusterter Actor-System muss sich darauf einigen, wer lebt. Der Failure Detector ist die Peer-spezifische Zustandsmaschine, die entscheidet:

kein Heartbeat für unreachableAfterMs

Heartbeat empfangen

Stille erreicht insgesamt downAfterMs

entfernt (empfohlen)

healthy

unreachable

down

  • healthy — Heartbeats kommen normal an.
  • unreachable — über dem Schwellwert; der Cluster vermeidet es, dorthin zu routen. Offiziell noch Mitglied; kann sich erholen.
  • down — über dem längeren Schwellwert; der Cluster betrachtet den Peer als endgültig weg. Löst Downing aus.

Der Detector ist pro Peer — verschiedene Peers können gleichzeitig in verschiedenen Zuständen sein. Das Gesamtverhalten des Clusters (Routing, Sharding, Singleton-Wahl) liest diese Zustände, um zu entscheiden, was zu tun ist.

{
heartbeatIntervalMs: 500, // alle 500ms einen Heartbeat senden
unreachableAfterMs: 2_000, // nach 2s Stille als unreachable markieren
downAfterMs: 5_000, // nach 5s Stille als down markieren
}

Für typische LAN-Cluster (1-10 Nodes, Sub-Millisekunden-Latenz) funktionieren diese Defaults gut. Sie ergeben:

  • 2-Sekunden-Erkennungsfenster für “dieser Peer hat möglicherweise Probleme.”
  • 5-Sekunden-Entscheidungsfenster für “dieser Peer ist endgültig weg.”

Überschreibe über das failureDetector-Feld in den Cluster-Settings:

const clusterOptions = ClusterOptions.create()
.withHost(host)
.withPort(port)
.withSeeds(seeds)
.withFailureDetector({
heartbeatIntervalMs: 1_000,
unreachableAfterMs: 5_000,
downAfterMs: 15_000,
});
Cluster.join(
system,
clusterOptions,
);

Wann tunen:

WorkloadRichtung
Cluster über Regionen hinweg (hohe RTT)Alle drei erhöhen. 100 ms RTT bedeuten, dass ein einzelner verpasster Heartbeat normales Rauschen ist.
Lokales Docker-Compose (Sub-ms RTT)Verringern für schnellere Failover-Tests. Vor dem Deploy für Produktion wieder hochstellen.
Netzwerk mit periodischen AussetzernunreachableAfterMs erhöhen, um Fehlalarme zu vermeiden, aber downAfterMs größer halten, damit ein echter Ausfall trotzdem erkannt wird.
Kostensensitiv (chatty Heartbeats)heartbeatIntervalMs erhöhen — bei 5s-Intervallen sind Gossip + Heartbeat < 1KB/s pro Peer.

Das Verhältnis downAfterMs / unreachableAfterMs (Standard ~2.5x) ist das Flap-Toleranzfenster: ein als unreachable markierter Peer kann sich zurück auf healthy erholen, ohne downed zu werden, solange ein Heartbeat innerhalb der Differenz eintrifft.

Jeder Gossip-Austausch zählt als Heartbeat.
Jede direkte Nachricht über den Cluster-Transport zählt ebenfalls.

Das Framework schickt keine separaten “Ping”-Nachrichten — jeglicher Cluster-Verkehr von einem Peer setzt dessen Last-Seen-Timestamp zurück. Gossip ist die zuverlässigste Quelle (regelmäßiges Intervall), aber Anwendungsnachrichten tragen auch bei.

Das bedeutet: ein Cluster mit sehr gesprächigen Actors bekommt besser Failure Detection (mehr Heartbeats); ein leerlaufender Cluster ist ganz auf Gossip angewiesen.

Der Detector gibt 'healthy' / 'unreachable' / 'down' zurück. Was der Cluster damit macht:

EntscheidungCluster-Verhalten
healthyNormales Routing. Kein Effekt.
unreachableMarkiere das Mitglied in der Mitgliedertabelle als unreachable. Router überspringen es; Sharding allokiert keine neuen Shards mehr darauf. Singleton-Manager wählt keinen Leader von einer unerreichbaren Seite.
downLöse Downing aus. Wenn eine Downing-Strategie konfiguriert ist, entscheidet sie, welche Adressen zwangsweise entfernt werden; der Cluster verkündet diese Nodes als removed.

Entscheidend: dass der Detector down entscheidet, entfernt den Peer nicht automatisch. Entfernung läuft durch die Downing-Strategie — der Detector ist das Signal, nicht die Aktion. Ohne Downing-Strategie bleibt eine down-Entscheidung nur beratend.

Jeder Node fährt seinen eigenen Detector und beobachtet seine eigenen Peers. Das bedeutet, dass zwei Nodes uneins sein können, ob ein dritter erreichbar ist:

gesunde Heartbeats

gesunde Heartbeats

Netzwerkpartition

(n1 sieht n3 unreachable;

n2 sieht n3 healthy)

n1

n2

n3

Der Cluster-Gossip propagiert diese pro-Node-Beobachtungen. Ein Mitglied gilt global als unreachable, wenn genug Peers es so melden — der Schwellwert ist in manchen Downing-Strategien konfigurierbar (siehe KeepMajority, KeepReferee).

Der eingebaute Detector des Clusters ist bewusst einfach — einfache Zeitschwellen, kein Tracking statistischer Varianz. Für LAN-Skala reicht das aus. Die failureDetector-Cluster-Option stellt nur seine Schwellwerte ein — sie nimmt Partial<FailureDetectorOptionsType> (die drei *Ms-Stellschrauben oben), keine Detector-Instanz; es gibt also keinen “eigenen Detector einhängen”-Hook an Cluster.join.

Falls dein Netzwerk Phi-Accrual braucht (varianzbewusst, adaptive Schwellwerte), bringt das Framework bereits einen mit: PhiAccrualFailureDetector (mit PhiAccrualOptions) ist eine öffentliche, exportierte Klasse, die die Verteilung der jüngsten Zwischenankunftszeiten verfolgt und einen kontinuierlichen Verdachtswert erzeugt. Er ist noch nicht als auswählbarer Detector in den Cluster eingebunden — der Cluster fährt immer den einfachen FailureDetector — daher nutzt du ihn vorerst eigenständig.

import { MemberUnreachable, MemberReachable } from 'actor-ts';
cluster.subscribe((evt) => {
if (evt instanceof MemberUnreachable) {
console.log(`${evt.member.address} als unreachable markiert`);
} else if (evt instanceof MemberReachable) {
console.log(`${evt.member.address} wieder als reachable markiert`);
}
});

Abonniere die Cluster-Events für Sichtbarkeit. In Produktion verdrahte das in Metriken — ein Histogramm der “unreachable-Dauern” zeigt, ob der Schwellwert dem tatsächlichen Aussetzerprofil deines Netzwerks entspricht.

MemberUnreachable ist ein Mitgliedschaftsübergang, und das macht es zum falschen Signal für zwei Fragen, die es zu beantworten scheint:

  • Es muss nicht die Beobachtung dieser Node sein. Der Member-Status wandert per Gossip, also feuert MemberUnreachable auch für einen Peer, von dem jemand anderes nichts mehr hört, während die eigenen Heartbeats dorthin normal ankommen.
  • Es feuert nur für ein Member, das up war. Ein Peer, der während joining, weakly-up oder leaving verstummt, erzeugt gar kein Reachability-Event — das Nächste, was du von ihm hörst, ist die Entfernung.

ReachabilityChanged ist das eigene Urteil des lokalen Detectors, ohne beide Einschränkungen:

import { ReachabilityChanged } from 'actor-ts';
cluster.subscribe((evt) => {
if (evt instanceof ReachabilityChanged) {
peerHealth.gauge({ peer: evt.address.toString() }).set(evt.reachable ? 1 : 0);
}
});

Es feuert nur bei Übergängen — einmal, wenn der Detector einen Peer aus den Augen verliert, einmal, wenn er ihn wiedersieht — und nie für einen Peer, der seit dem ersten Kontakt gesund war. Das Urteil wird einmal pro heartbeatIntervalMs neu berechnet und kippt bei unreachableAfterMs, ist also die früheste Warnung des Detectors, lange vor jeder Downing-Entscheidung.

Dieses Urteil gegen den gegossipten status des Members zu halten, ist der Weg, eine Partition von einem toten Peer zu unterscheiden: eine Node, die alle anderen noch erreichen, ist ein Leitungs- und kein Host-Problem. Das Event trägt keine Observer-Menge — “welche anderen Nodes erreichen X ebenfalls nicht” bräuchte eine Observer-zu-Subjekt- Tabelle auf dem Draht, und Gossip trägt eine flache Member-Liste.

Die FailureDetector API-Referenz deckt die vollständige Oberfläche ab.