Μετάβαση στο περιεχόμενο

Redis και Valkey

Το Redis είναι το προεπιλεγμένο backend. Το RedisBackend λειτουργεί με Redis 5 ή νεότερο, και το ValkeyBackend με Valkey.

import { RedisBackend, Scheduler, ValkeyBackend } from 'anarchon';
new Scheduler({ backend: new RedisBackend({ redis: 'redis://localhost:6379' }) });
new Scheduler({ backend: new RedisBackend({ redis: 'redis://cache.internal:6379' }) });
new Scheduler({ backend: new ValkeyBackend({ valkey: { host: 'localhost', port: 6390 } }) });

Το ValkeyBackend είναι ένα RedisBackend που δέχεται μόνο τις στρατηγικές που υποστηρίζει το Valkey, οπότε το strategy: 'idmp' είναι σφάλμα τύπου αντί για σφάλμα από το start(). Η επιλογή σύνδεσής του είναι valkey αντί για redis, και δεν έχει ρυθμίσεις idmp. Όλα τα υπόλοιπα αυτής της σελίδας ισχύουν και για τα δύο.

Η παράμετρος redis δέχεται ένα URL, ένα αντικείμενο επιλογών (options object) ή έναν client.

new Scheduler({ backend: new RedisBackend({ redis: process.env.REDIS_URL }) }); // rediss:// για TLS· χρήστης, κωδικός και βάση δεδομένων μπαίνουν στο URL
new Scheduler({ backend: new RedisBackend({ redis: { host: 'cache', port: 6379, password: process.env.REDIS_PASSWORD, db: 1 } }) });

Όταν περνάτε ένα URL ή επιλογές, το anarchon ανοίγει τη σύνδεση με το redis (node-redis) και την κλείνει κατά την κλήση του stop().

import { Redis } from 'ioredis';
const redis = new Redis(process.env.REDIS_URL);
const scheduler = new Scheduler({ backend: new RedisBackend({ redis }) }); // το stop() τον αφήνει ανοιχτό

Αναγνωρίζονται οι εξής clients: ioredis, iovalkey, node-redis (redis) και Valkey GLIDE. Οποιοσδήποτε άλλος λειτουργεί εφόσον διαθέτει μέθοδο send(command, args) που επιστρέφει την απόκριση.

Επιλογές του new RedisBackend(). Το new ValkeyBackend() δέχεται τις ίδιες, με valkey στη θέση του redis και χωρίς τη στρατηγική 'idmp':

Επιλογή Προεπιλογή Σημασία
redis localhost:6379 Σύνδεση ή client
strategy 'lease' Πώς επιλέγεται ένας νικητής: ένα όνομα, ή { name, ...ρυθμίσεις } (παρακάτω)
prefix 'cron:' Πρόθεμα κάθε κλειδιού

Οι ρυθμίσεις μιας στρατηγικής μπαίνουν μέσα της, ώστε μια επιλογή που δεν χρησιμοποιεί η στρατηγική να μην μπορεί να οριστεί κατά λάθος: το { name: 'lease', windowMs: 1 } είναι σφάλμα τύπου, και σφάλμα όταν δημιουργείται το backend.

import { RedisBackend, Scheduler } from 'anarchon';
const backend = new RedisBackend({
redis: process.env.REDIS_URL,
strategy: { name: 'script', windowMs: 10 * 60_000 },
prefix: 'billing:cron:',
});
new Scheduler({ backend });

Μόνο το lease δίνει στον scheduler μισθώσεις. Με τις άλλες στρατηγικές, οι επιλογές μίσθωσης (overlap, maxAttempts και catchUpMs στις εργασίες, leaseMs και heartbeatMs στον scheduler) δεν προσφέρονται από τους τύπους και απορρίπτονται αν οριστούν: δείτε Brokers και μισθώσεις.

Μια στρατηγική είναι η εντολή που επιτρέπει σε ακριβώς ένα instance να κερδίσει. Το Redis εκτελεί εντολές και Lua scripts μία-μία τη φορά, επομένως οποιαδήποτε μεμονωμένη εντολή ή script που ελέγχει ταυτόχρονα αν «είναι δεσμευμένη αυτή η εκτέλεση;» και τη δεσμεύει, είναι ατομική.

Στρατηγική Μισθώσεις (Leases) Λειτουργεί σε Απομνημονεύει μια εκτέλεση για
lease (προεπιλογή) ναι Redis 5+, Valkey όσο διάστημα θα μπορούσε να τη ζητήσει ένα καθυστερημένο claim
stream-id όχι Redis 5+, Valkey για πάντα
idmp όχι Redis 8.6+ μόνο strategy.durationSec (5 λεπτά)
script όχι Redis 5+, Valkey strategy.windowMs (5 λεπτά)

Χρησιμοποιήστε τη στρατηγική lease εκτός αν έχετε λόγο να μην το κάνετε. Είναι η μόνη στρατηγική με backups, προστασία επικάλυψης, επαναπροσπάθειες και ανάκτηση (catch-up). Οι άλλες αποφασίζουν μόνο ποιος εκτελεί κάθε φορά· διατηρούνται για ελάχιστες ρυθμίσεις και για σύγκριση.

Ένα Lua script διαβάζει την κατάσταση της εκτέλεσης και αποφασίζει:

flowchart TD
  claim["claim(job, slot, token)"] --> has{"κατάσταση για αυτή την εκτέλεση;"}
  has -- "καμία" --> overlap{"παλαιότερη εκτέλεση της εργασίας<br/>με ενεργή μίσθωση;"}
  overlap -- "ναι" --> ovl["απάντηση: overlap"]
  overlap -- "όχι" --> won1["αποθήκευση attempt 1, token, λήξη μίσθωσης<br/>απάντηση: won"]
  has -- "finished" --> done["απάντηση: done"]
  has -- "running" --> alive{"λήξη μίσθωσης στο μέλλον;"}
  alive -- "ναι" --> running["απάντηση: running<br/>(ο καλών γίνεται εφεδρικός)"]
  alive -- "όχι" --> attempts{"απομένουν προσπάθειες;"}
  attempts -- "ναι" --> won2["αποθήκευση attempt + 1, token του καλούντος, νέα μίσθωση<br/>απάντηση: won"]
  attempts -- "όχι" --> giveup["σήμανση interrupted<br/>απάντηση: done"]

Όσο η εργασία εκτελείται, ο νικητής στέλνει heartbeat που παρατείνει τη λήξη της μίσθωσης, και στο τέλος σημειώνει την εκτέλεση ως ολοκληρωμένη. Και τα δύο λειτουργούν μόνο με το token της τρέχουσας προσπάθειας.

Το Μισθώσεις καλύπτει ολόκληρο τον μηχανισμό.

Κάθε instance προσθέτει μια εγγραφή στο stream της εργασίας με τον χρόνο εκτέλεσης ως entry ID. Το Redis απορρίπτει ένα ID που δεν είναι μεγαλύτερο από το τελευταίο του stream, οπότε μόνο η πρώτη XADD επιτυγχάνει.

A: XADD cron:sync:runs 1791633610000-0 slot ... token a1 -> "1791633610000-0" A runs it
B: XADD cron:sync:runs 1791633610000-0 slot ... token b7 -> ERR The ID specified in XADD is equal
or smaller than the target stream top item

Είναι η απλούστερη στρατηγική και απομνημονεύει τις εκτελέσεις για πάντα, επειδή τα IDs μόνο αυξάνονται.

Το Redis 8.6 πρόσθεσε idempotent XADD. Το Redis διατηρεί την πρώτη εγγραφή για ένα κλειδί idempotency και απαντά στις επαναλήψεις με το ID εκείνης της εγγραφής.

A: XADD cron:sync:runs IDMP sync 1791633610000 * slot ... token a1 -> "1791633610012-0" (stored)
B: XADD cron:sync:runs IDMP sync 1791633610000 * slot ... token b7 -> "1791633610012-0" (A's entry)

Στη συνέχεια, κάθε instance διαβάζει εκείνη την εγγραφή και συγκρίνει τα tokens. Με το ValkeyBackend, το strategy: 'idmp' δεν μεταγλωττίζεται. Με το RedisBackend σε Valkey ή παλαιότερο Redis, η μέθοδος start() αποτυγχάνει με μήνυμα που παραπέμπει στις υπόλοιπες στρατηγικές.

Ρύθμιση του { name: 'idmp' } Προεπιλογή Σημασία
durationSec 300 Για πόσο χρόνο το Redis απομνημονεύει το ID μιας εκτέλεσης (1 έως 86400)
maxSize 1000 Πόσα IDs εκτελέσεων διατηρεί το Redis ανά εργασία (1 έως 10000)
new RedisBackend({ strategy: { name: 'idmp', durationSec: 900, maxSize: 5_000 } });

Η ίδια συμπεριφορά με το idmp για servers που δεν το υποστηρίζουν. Ένα Lua script σαρώνει τις πρόσφατες εγγραφές του stream για αυτόν τον χρόνο εκτέλεσης και προσθέτει το claim μόνο αν απουσιάζει.

Ρύθμιση του { name: 'script' } Προεπιλογή Σημασία
windowMs 300000 Για πόσο απομνημονεύεται μια δεσμευμένη εκτέλεση. Πρέπει να είναι μεγαλύτερο από το jitterMs + maxLateMs μιας εργασίας
new ValkeyBackend({ strategy: { name: 'script', windowMs: 10 * 60_000 } });

Με τη στρατηγική lease, κάθε εργασία έχει πέντε κλειδιά. Μοιράζονται ένα hash tag (το μέρος μέσα στις αγκύλες), ώστε το Redis Cluster να τα διατηρεί στον ίδιο κόμβο και τα scripts να μπορούν να επεξεργαστούν όλα τους:

Κλειδί Τύπος Περιεχόμενο
{cron:<job>}:state hash Ανά εκτέλεση: attempt, token, status, runner, λήξη μίσθωσης
{cron:<job>}:active set Εκτελέσεις που δεν έχουν ολοκληρωθεί ακόμα
{cron:<job>}:slots sorted set Κάθε εκτέλεση στο state, για εκκαθάριση (trim) των παλαιότερων
{cron:<job>}:runs stream Μία εγγραφή ανά προσπάθεια (ιστορικό)
{cron:<job>}:results sorted set Ένα αποτέλεσμα ανά προσπάθεια (ιστορικό)

Με τις στρατηγικές μόνο για claim υπάρχουν τρία:

Κλειδί Τύπος Περιεχόμενο
cron:<job>:runs stream Μία εγγραφή ανά διεκδικηθείσα εκτέλεση
cron:<job>:manual stream Μία εγγραφή ανά εκτέλεση runNow()
cron:<job>:results sorted set Ένα αποτέλεσμα ανά εκτέλεση

Το cron: είναι το prefix. Κάθε στρατηγική γράφει επίσης ένα κλειδί, cron:format: τη μορφή του store, ώστε τα δεδομένα ενός νεότερου anarchon να μη διαβάζονται ποτέ λάθος. Ένα backend αρνείται να ξεκινήσει όταν βρίσκει μορφή νεότερη από όσες διαβάζει (ANARCHON_STORE_FORMAT).

Οι χρόνοι λήξης των μισθώσεων υπολογίζονται με την εντολή TIME του ίδιου του Redis, μέσα στο script. Τα instances δεν συγκρίνουν ποτέ τα ρολόγια τους μεταξύ τους, επομένως μια διαφορά ώρας μεταξύ δύο instances δεν μπορεί να κάνει μια ενεργή μίσθωση να φαίνεται ληγμένη.

  • Cluster: με τη στρατηγική lease όλα τα κλειδιά μιας εργασίας μοιράζονται το ίδιο hash tag, επομένως κάθε script αφορά έναν μοναδικό κόμβο. Περάστε έναν cluster client που δημιουργήσατε εσείς (έναν ioredis Cluster ή έναν GLIDE cluster client). Οι δοκιμές του ίδιου του anarchon εκτελούνται σε μεμονωμένους servers.
  • Sentinel και replicas: στοχεύστε το anarchon στον πρωτεύοντα κόμβο (primary). Τα claims είναι εγγραφές (writes).

Το ιστορικό περικόπτεται σε περίπου historySize εκτελέσεις ανά εργασία (προεπιλογή 1000), και βάσει του historyTtlMs εφόσον το ορίσετε. Η κατάσταση μιας εκτέλεσης δεν περικόπτεται ποτέ όσο βρίσκεται σε εξέλιξη, ή όσο ένα καθυστερημένο claim θα μπορούσε να τη ζητήσει.