Zum Inhalt springen
Deutsch

Worker-Mesh

JavaScript ist pro Actor-System Single-Threaded. Für Parallelität innerhalb eines einzelnen OS-Prozesses lässt das Worker-Mesh des Frameworks mehrere ActorSystems laufen — eines pro Worker-Thread — die alle am selben Cluster über einen MessageChannel-Transport teilnehmen.

Hauptprozess (einzelner OS-Prozess)

ActorSystem 'main'

Hauptthread

ActorSystem 'w1'

Worker-Thread 1

ActorSystem 'w2'

Worker-Thread 2

ActorSystem 'w3'

Worker-Thread 3

Jeder ist aus Sicht des Clusters ein separater Cluster-Node — Gossip + Mitgliedschaft + Sharding gelten alle. Die Kommunikation zwischen ihnen läuft über In-Process-MessageChannel (keine Serialisierung zu Bytes, kein TCP).

Zwei Hauptszenarien:

  1. CPU-gebundene Parallelität in einem Prozess — actor-ts ist pro System Single-Threaded; Multi-Threading braucht mehrere Systeme. Worker-Mesh verteilt sie.
  2. Isolation innerhalb eines Prozesses — ein “Worker”, der ausfällt, reißt das Hauptsystem nicht mit.

Für Multi-Process-Parallelität (separate OS-Prozesse) nutze den regulären Cluster + TCP-Transport. Worker-Mesh ist speziell für den In-Process-Fall.

// main.ts — Hauptthread
import { Worker } from 'node:worker_threads';
import { ActorSystem, Cluster, MessageChannelTransport } from 'actor-ts';
const channel = new MessageChannel();
const w1 = new Worker('./worker.js', {
workerData: { mainPort: channel.port2 },
transferList: [channel.port2],
});
const transport = new MessageChannelTransport({
self: 'main',
ports: [channel.port1],
});
const system = ActorSystem.create('main');
await Cluster.join(system, {
host: 'main',
port: 0,
seeds: ['main'],
transport,
});
// worker.js — läuft im Worker-Thread
import { parentPort, workerData } from 'node:worker_threads';
import { ActorSystem, Cluster, MessageChannelTransport } from 'actor-ts';
const transport = new MessageChannelTransport({
self: 'w1',
ports: [workerData.mainPort],
});
const system = ActorSystem.create('w1');
await Cluster.join(system, {
host: 'w1',
port: 0,
seeds: ['main'],
transport,
});
// Ab hier ist w1 einfach ein weiterer Cluster-Node

Für mehrere Worker braucht jedes Paar einen MessageChannel. Ein voll vermaschtes Mesh mit 4 Workern braucht 6 Channels (binomial(4,2)).

Der MessageChannelTransport des Frameworks akzeptiert ein Array von Ports:

new MessageChannelTransport({
self: 'main',
ports: [
portToW1,
portToW2,
portToW3,
],
});

Jeder Port zielt auf einen Peer.

Für größere Meshes ist die Stern-Topologie (alle reden mit main; main leitet weiter) einfacher — nur N-1 Channels nötig. Aber das macht main zum Flaschenhals.

TCP-Transport: MessageChannelTransport:
- Sockets, Framing - postMessage zwischen Threads
- Serialisierte Bytes - Structured Cloning (kein JSON)
- Netzwerklatenz - Sub-Mikrosekunde
- Cross-Host - Nur derselbe Prozess

Nachrichten zwischen Worker-Systemen gehen durch Structured Clone — schneller als JSON.stringify + parse und erhalten mehr Typen (Map, Set, Date etc.).

// 4-Worker-Mesh; Sharding verteilt Entities über sie:
sharding.start({
typeName: 'order',
entityProps: ...,
extractEntityId: (msg) => msg.id,
numShards: 16,
});

Der Koordinator (auf main) allokiert Shards auf die 4 Worker. CPU-gebundene Entity-Arbeit parallelisiert über Cores.

// Worker, der GPU-gebundene Jobs handhabt:
system.spawn(Props.create(() => new GpuJobActor()), 'gpu-jobs');
// Abstürze in diesem Worker bleiben isoliert von main + anderen Workern

Ein abstürzender Worker reißt das Hauptsystem nicht mit — separate Event-Loops.

  • Cluster-Überblick — das Cluster-Modell, an dem das Worker-Mesh teilnimmt.
  • Transports — die Transport-Schnittstelle, die MessageChannelTransport implementiert.
  • Sharding — der Hauptnutzer der Mesh-Parallelität.