Joining und Seeds
Ein Node tritt einem Cluster bei, indem er einen Seed-Node
kontaktiert. Der Seed sendet per Gossip seine aktuelle
Mitgliedschaftssicht zurück; der Joiner wird als joining
hinzugefügt, propagiert per Gossip, und sobald der Leader ihn sieht
(plus Konvergenz), wechselt er zu up.
Diese Seite behandelt die Mechanik dieses Handshakes plus die Seed-Discovery-Ebene darüber.
Der einfachste Fall — explizite Seeds
Abschnitt betitelt „Der einfachste Fall — explizite Seeds“import { ActorSystem, Cluster, ClusterOptions } from 'actor-ts';
const system = ActorSystem.create('my-app');
const clusterOptions = ClusterOptions.create() .withHost('10.0.0.5') .withPort(2552) .withSeeds(['10.0.0.5:2552', '10.0.0.6:2552', '10.0.0.7:2552']);const cluster = await Cluster.join( system, clusterOptions,);Drei Seeds. Der Joiner kontaktiert sie der Reihe nach, bis einer
antwortet. Sobald irgendein Seed akzeptiert, propagiert der Gossip
des Clusters das neue Mitglied; Konvergenz zu up geschieht
innerhalb weniger Sekunden in einem gesunden Netzwerk.
Die Seed-Liste ist nur ein Bootstrap-Hinweis — sobald der Node beigetreten ist, lernt er alle anderen Peers per Gossip kennen. Seeds müssen nach dem Join nicht mehr besonders sein.
Konfiguration
Abschnitt betitelt „Konfiguration“type ClusterOptionsType = { host: string; // Adresse dieses Nodes port: number; // TCP-Port dieses Nodes seeds?: string[]; // Peer-Adressen fürs Bootstrap roles?: string[]; // Rollen-Tags failureDetector?: Partial<...>; transport?: Transport; gossipIntervalMs?: number; seedRetryIntervalMs?: number; // Retry-Intervall, falls kein Seed antwortet // ...};Die seed-bezogenen Knöpfe:
| Einstellung | Standard | Was |
|---|---|---|
seeds | [] | Liste von "host:port"-Strings. Leer = “ich bin der erste”. |
seedRetryIntervalMs | 3000 | Falls kein Seed antwortet, wiederhole die Liste so oft, bis einer antwortet. |
selfElection | 'immediate' | Wann dieser Node allein einen Cluster bilden darf: 'immediate' (nur bei leerer Seed-Liste), 'never', oder eine Frist in Millisekunden. Wird vom Cluster-Bootstrap gesetzt. |
Der erste Node
Abschnitt betitelt „Der erste Node“const clusterOptions = ClusterOptions.create() .withHost('0.0.0.0') .withPort(2552) .withSeeds([]);const cluster = await Cluster.join( system, clusterOptions,);Eine leere seeds-Liste (oder eine, die komplett unerreichbar ist)
bedeutet, dass dieser Node den Cluster selbst bootstrapt. Er
befördert sich automatisch zum Leader; künftige Joiner kontaktieren
ihn.
Das macht die Single-Node-Entwicklung trivial — keine zu pflegende Seed-Liste. Füge später einen zweiten Node hinzu, indem du ihm die Adresse des ersten als Seed gibst.
Für Produktion bestimme einen Node mit leerer Seed-Liste und gib den übrigen dessen Adresse — oder, besser, nutze den Cluster-Bootstrap, der einen festgelegten ersten Node überflüssig macht.
Die symmetrische Seed-Liste startet nicht kalt
Abschnitt betitelt „Die symmetrische Seed-Liste startet nicht kalt“Es liegt nahe, jedem Node dieselbe Liste mit allen Nodes zu geben. Diese Konfiguration bildet nie einen Cluster:
Cluster entfernt die eigene Adresse aus der Seed-Liste, also bleibt
bei einer symmetrischen Liste keinem Node die leere Liste, die
selfElection: 'immediate' verlangt. Jeder Node bleibt für immer
joining; es gibt keinen Leader, und der Leader ist es, der
joining → up befördert.
Zwei Auswege:
- Ein festgelegter erster Node mit
seeds: [], der Rest zeigt auf ihn. Einfach, aber dieser Node ist beim Start etwas Besonderes — was Container und Autoscaler unhandlich machen. - Cluster-Bootstrap — jeder Node bekommt dieselbe Konfiguration, und das Framework wählt genau einen aus, der den Gleichstand bricht. Das ist die Wahl, sobald Nodes gleichzeitig starten.
Die Retry-Schleife über seedRetryIntervalMs bleibt wichtig, löst aber
ein anderes Problem: einen Seed, der irgendwann erreichbar ist. Ein
erstes Mitglied kann sie nicht herbeizaubern.
Seed-Discovery — jenseits einer statischen Liste
Abschnitt betitelt „Seed-Discovery — jenseits einer statischen Liste“Eine hartcodierte Seed-Liste reicht für Tests und kleine Cluster. Für Produktion, in der Nodes dynamische IPs haben (Container, K8s Pods), nutze einen Seed-Provider:
| Provider | Wann |
|---|---|
| Config | Statische Liste (der Fall oben). |
| DNS | Löst _actor-ts._tcp.example.com SRV-Records auf. |
| Kubernetes API | Listet Pods, die einem Label-Selector entsprechen. |
| Aggregate | Fällt durch mehrere Provider durch (z. B. K8s, dann DNS). |
import { KubernetesApiSeedProvider, KubernetesApiSeedProviderOptions } from 'actor-ts/discovery';
const kubernetesApiSeedProviderOptions = KubernetesApiSeedProviderOptions.create() .withNamespace('default') .withServiceName('actor-ts') .withPort(2552);const seedProvider = new KubernetesApiSeedProvider( kubernetesApiSeedProviderOptions,);
const seeds = await seedProvider.discover();
const clusterOptions = ClusterOptions.create() .withHost(process.env.POD_IP!) .withPort(2552) .withSeeds(seeds);const cluster = await Cluster.join( system, clusterOptions,);Der Provider liefert eine Momentaufnahme von Seed-Adressen; das Framework nutzt sie, um den Join zu bootstrappen. Siehe Discovery-Überblick für das Seed-Provider-Modell.
Den Join-Fortschritt beobachten
Abschnitt betitelt „Den Join-Fortschritt beobachten“import { SelfUp, MemberUp } from 'actor-ts';
cluster.subscribe((evt) => { if (evt instanceof SelfUp) { console.log(`dieser Node ist jetzt Up`); } else if (evt instanceof MemberUp) { console.log(`Peer ${evt.member.address} hat Up erreicht`); }});Zwei zentrale Events:
SelfUpfeuert einmal, wenn dieser Node aufupübergeht. Nützliches Gate, um Arbeit zu starten, die Cluster-Mitgliedschaft erfordert.MemberUpfeuert jedes Mal, wenn irgendein Mitglieduperreicht.
Für Startup-Logik, die andere Mitglieder braucht (“warte, bis
mindestens 3 Nodes up sind, bevor Traffic bedient wird”), zähle
MemberUps nach SelfUp.
Was schiefgehen kann
Abschnitt betitelt „Was schiefgehen kann“Wohin als Nächstes
Abschnitt betitelt „Wohin als Nächstes“- Cluster-Überblick — das größere Bild.
- Cluster-Bootstrap — Stable Observation und Initial-Seed-Wahl für gleichzeitige Starts.
- Weakly-up — Gradual-Join-Semantik für langsame Konvergenz.
- Failure Detector — wie Heartbeats die Mitgliedschaftssicht nach dem Join frisch halten.
- Discovery-Überblick — Seed-Provider für dynamische Umgebungen.
- Downing-Strategien — Split-Brain-Auflösung nach der Cluster-Bildung.
