Zum Inhalt springen
Deutsch

Koordination im Überblick

Manchmal braucht der Cluster eine Single-Holder-Garantie, die Mechanismen, die nur auf Membership beruhen, nicht liefern können:

  • Der ClusterSingleton soll während einer Netzwerk-Partition niemals zwei Instanzen spawnen.
  • Der Sharding-Koordinator auf der Leader-Seite einer Partition soll keine Allocations vergeben, wenn eine andere Seite auch einen Leader haben könnte.

Die Lease-API gibt dir diese Garantie. Ein Lease ist ein verteilter Lock mit TTL — höchstens ein Prozess hält ihn gleichzeitig, mit einem Backend (Kubernetes, etcd, dein eigenes), das die Single-Holder-Constraint liefert.

interface Lease {
acquire(): Promise<boolean>;
release(): Promise<void>;
checkAlive(): boolean;
onLost(handler: (reason: string) => void): () => void;
}

Vier Methoden:

  • acquire() — versuche, den Lease zu beanspruchen. Resolved mit true bei Erfolg, mit false bei Contention (jemand anders hat ihn).
  • release() — gib das Eigentum freiwillig ab. No-op, wenn nicht gehalten.
  • checkAlive() — billiger, lokaler “halte ich diesen Lease noch?”-Check.
  • onLost(cb) — Registriere einen Callback, der feuert, wenn das Eigentum unerwartet verloren geht (TTL ohne Renewal abgelaufen, ein anderer Halter hat übernommen).

Das Backend implementiert den Vertrag. Das eingebaute KubernetesLease des Frameworks nutzt native K8s-Lease-Ressourcen; InMemoryLease ist die Test-/Dev-Option.

Zwei Produktions-Szenarien:

SzenarioWarum ein Lease hilft
Cluster-SingletonVerhindert Doppel-Leadership während einer Partition. Siehe Singleton mit Lease.
Sharding-KoordinatorVerhindert, dass zwei Koordinatoren konfliktierende Allocations vergeben. Siehe Sharding mit Lease.

Für die meisten Cluster reicht eine Downing-Strategie. Leases sind die paranoid-sichere Option:

  • Nur Downing-Strategie: der Cluster wählt während einer Partition einen Gewinner; bei den meisten Apps reicht das.
  • Downing-Strategie + Lease: eine zusätzliche Prüfung, die Edge-Case-Doppel-Leadership verhindert, selbst wenn Downing fehlzündet.

Wenn du dir die operativen Kosten leisten kannst (K8s-Namespace-Permissions, etcd-Cluster etc.), aktiviere Leases für jeden Singleton- oder Sharding-Aufbau, in dem Doppel-Ausführung echten Schaden anrichten würde — Kunde doppelt belasten, Stream-Event doppelt publizieren.

acquire erfolgreich

Ticks vergehen

Renew erfolgreich

Renew fehlgeschlagen oder explizites Release

onLost-Callback feuert

explizites Release

not_held

acquired

renewing

Der Ablauf:

  1. Irgendein Actor (typischerweise ein Singleton Manager) ruft lease.acquire().
  2. Das Backend versucht, diesen Owner einzutragen. Erfolg → gibt true zurück.
  3. Während gehalten, renewt das Backend alle renewalIntervalMs (typischerweise ttl / 3).
  4. Wenn das Renewal fehlschlägt (Netz-Hänger, Backend down, Lease übernommen), feuert onLost mit dem Grund.
  5. Der Actor gibt eigentumsabhängigen State sofort frei.

Die TTL ist kritisch — ein Halter, der ohne release abstürzt, verliert den Lease automatisch, wenn die TTL abläuft. Andere Bewerber können dann acquiren.

interface LeaseSettings {
name: string; // eindeutiger Lease-Identifier
owner: string; // Identifier dieses Halters (Pod-Name / UUID)
ttlMs: number; // automatisches Ablaufen nach so vielen ms ohne Renewal
renewalIntervalMs?: number; // typischerweise ttl/3
acquireRetries?: number; // max Versuche pro acquire()
acquireRetryDelayMs?: number; // Verzögerung zwischen Versuchen
}
SettingTypische Werte
ttlMs15-30 s. Kurz genug, um von einem abgestürzten Halter zu erholen, lang genug, um Gossip-/Renewal-Verzögerungen zu tolerieren.
renewalIntervalMs~ttl / 3. Oft genug renewen, dass ein einzelnes fehlgeschlagenes Renew den Lease nicht verliert.
acquireRetries3-10 für Produktion.
acquireRetryDelayMs100-1000 ms.
BackendVerwendung
InMemoryLeaseTests und Dev. In-Process; nicht wirklich verteilt.
KubernetesLeaseProduktion auf K8s. Nutzt native K8s-Lease-CRDs.

Für Non-K8s-Produktions-Deployments würdest du das Lease-Interface gegen deinen Coordination-Service (etcd, Consul, Zookeeper) implementieren. Das Interface ist klein — ~50 Zeilen Glue.

import { ClusterSingletonManager, KubernetesLease, Props } from 'actor-ts';
const lease = new KubernetesLease({
name: 'my-app-singleton-lease',
owner: process.env.POD_NAME!,
ttlMs: 30_000,
renewalIntervalMs: 10_000,
namespace: process.env.K8S_NAMESPACE!,
});
system.spawn(
ClusterSingletonManager.props({
cluster,
typeName: 'job-scheduler',
singletonProps: Props.create(() => new JobScheduler()),
lease, // ← Split-Brain-Schutz
}),
'singleton-manager-job-scheduler',
);

Der Singleton Manager:

  • Versucht, den Lease zu acquiren, bevor er den Singleton spawnt.
  • Renewt ihn, während er am Leben ist.
  • Released ihn bei einem graceful Shutdown.
  • Reagiert auf lease.onLost(...), indem er seinen Singleton stoppt.