Zurück zur Startseite

Redis Lua Queue: Reihenfolge in Partitionen und Worker-Pool

Der Artikel beschreibt die Implementierung einer Aufgabenwarteschlange auf Redis mit Lua-Skripten, um die Reihenfolge in Partitionen mit einem gemeinsamen Worker-Pool zu gewährleisten. Unterstützung für verzögerte Nachrichten, Wiederholungen, Performance-Benchmarks. Geeignet für Dienste mit hoher Last ohne Kafka.

Lua-Skripte in Redis: Warteschlange mit Reihenfolge und Verzögerung
Advertisement 728x90

Redis-Warteschlange mit Lua: Garantierte Reihenfolge in Partitionen mit einem gemeinsamen Worker-Pool

In Hochlastsystemen erfordert die Nachrichtenverarbeitung eine strikte Reihenfolge innerhalb logischer Partitionen über Hunderttausende von Schlüsseln hinweg. Ein begrenzter Worker-Pool muss die Last gleichmäßig verteilen, ohne durch schwere Partitionen blockiert zu werden. Unterstützung für Wiederholungen und verzögerte Aufgaben wird über Redis mit Lua-Skripten in der smart-redis-queue-Bibliothek implementiert.

Die Lösung nutzt die Atomarität von Lua für Operationen auf ZSET, HASH und STRING ohne Race Conditions. Schlüssel werden mit dem Präfix queue:{queueName}: gebildet. Geordnete Partitionen (Code mit Präfix !) werden exklusiv mit Locking verarbeitet, während reguläre parallel abgearbeitet werden.

Vergleich mit Kafka und RabbitMQ

Kafka gewährleistet Reihenfolge innerhalb von Topic-Partitionen und skaliert mit Consumer-Gruppen, fixiert jedoch die Partitionenzuweisungen an Consumer. Ein dynamischer Worker-Pool, der Aufgaben aus beliebigen bereiten Partitionen entnimmt, erfordert andere Semantiken. TTL in Kafka ersetzt keine präzise Zeitplanung bis Zeitpunkt T.

Google AdInline article slot

RabbitMQ unterstützt TTL und DLQ, aber die strikte Reihenfolge bei mehreren Consumern wird durch Round-Robin gebrochen. Separate Warteschlangen für Millionen von Schlüsseln skalieren nicht.

Redis + Lua bietet eine leichte Alternative: Ein Round-Trip pro atomarer Operation, kein Cluster oder komplexes Monitoring erforderlich.

Schlüsselstruktur

| Schlüssel | Struktur | Zweck |

Google AdInline article slot

|------|-----------|------------|

| queue:{name}:partitions | SET | Aktive Partitionen |

| queue:{name}:partition:{code}:{priority} | ZSET | Aufgabenwarteschlange nach Priorität, Score = Verfügbarkeitszeit (ms) |

Google AdInline article slot

| queue:{name}:partition:{code}:priorities | ZSET | Prioritätsstufen |

| queue:{name}:payload:{taskId} | STRING | Nutzdaten |

| queue:{name}:partition:{taskId} | STRING | Partitionscode |

| queue:{name}:consumer:{id}:tasks | HASH | Aufgaben in Bearbeitung |

| queue:{name}:partition:{code}:lock | STRING | Sperre für geordnete Partitionen |

| queue:{name}:partition:{code}:block | STRING (TTL) | Temporäre Blockade (Rate Limit) |

Aufgabenlebenszyklus

  • Veröffentlichen: Idempotente Hinzufügung mit SET NX, Schreiben in ZSET mit Score = geplant (ms). Partition wird zum SET hinzugefügt.
local ok = redis.call('SET', payloadKey, payload, 'NX')
if ok then
    redis.call('ZADD', partitionQueueKey, scheduled, taskId)
    redis.call('SADD', partitionsKey, partitionCode)
end
  • Abrufen: Heartbeat, Iteration über Partitionen, Prüfung von Lock/Block für geordnete. Entnahme nach absteigenden Prioritäten (ZREVRANGE), Kopf der ZSET wenn scheduled <= jetzt. Verschieben in HASH des Consumers.
  • Bestätigen: Entfernen aus HASH, Nutzdaten, Schlüssel. Sperre freigeben, wenn geordneter Zähler = 0.
  • Ablehnen: Zurück zu ZSET mit neuem Score. Für geordnete — Priorität +1, reject_seq für Tie-Break. Verarbeitung lehnt den gesamten Batch einer geordneten Partition ab, wenn die erste Aufgabe abgelehnt wird.

Leistungsbenchmarks

Tests mit Go 1.22+, Redis 5, Intel i7-13700H.

Veröffentlichung (Aufgaben/s):

  • 1 Aufgabe: 17–23k
  • Batch 10: 80–92k
  • Batch 100: 100–117k

Batching erhöht den Durchsatz über einen einzigen Lua-Aufruf.

Verarbeitung durch einen Worker (Aufgaben/s):

| Prefetch | ns/op | Aufgaben/s |

|----------|--------|---------|

| 1 | 80–103 µs | 9.7–12.5k |

| 5 | 57–67 µs | 15–17.5k |

Parallel:

  • 2 Worker: 28–30k/s
  • 8 Worker: 23–26k/s

Skalierung ist nicht linear aufgrund von Redis-Konflikten.

Fehlerbehandlung

  • Heartbeat mit TTL ~120s.
  • Live-Consumer scannt tote über verteilte Sperre.
  • Aufgaben aus HASH toter Consumer werden zurück in die Warteschlange gegeben (geordnete mit +Priorität).

Rate Limit: RejectWithDelay setzt Block mit TTL.

Empfehlung: Prioritätsschritte von 100+ um Kollisionen mit Ablehnungsinkrementen zu vermeiden.

Wichtige Erkenntnisse

  • Lua-Atomarität gewährleistet keine Race Conditions beim Verschieben von Aufgaben zwischen ZSET und HASH.
  • Geordnete Partitionen halten Reihenfolge über exklusive Sperre und Ablehnungslogik.
  • Batching bei Veröffentlichung/Abruf steigert Durchsatz auf 100k+ Aufgaben/s.
  • Automatische Wiederherstellung nach Worker-Ausfällen ohne Aufgabenverlust.
  • Geeignet für Szenarien mit Millionen Partitionen ohne Cluster-Overhead.

— Editorial Team

Advertisement 728x90

Weiterlesen