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

Εγγυήσεις και όρια

Είναι εύκολο να δείξει κανείς υπερβολική εμπιστοσύνη σε έναν scheduler. Αυτή η σελίδα δηλώνει με ακρίβεια τι εγγυάται το anarchon και πού σταματά η εγγύηση.

Για μία εργασία και μία χρονική θυρίδα, μόνο ένα instance κερδίζει κάθε προσπάθεια. Αυτό ισχύει σε κάθε backend, επειδή η διεκδίκηση είναι ένα ενιαίο ατομικό βήμα στο store.

Δεύτερη προσπάθεια μόνο αφού η πρώτη σταματήσει να ανανεώνει

Ενότητα με τίτλο «Δεύτερη προσπάθεια μόνο αφού η πρώτη σταματήσει να ανανεώνει»

Backends μίσθωσης. Μια εκτέλεση λαμβάνει άλλη προσπάθεια μόνο όταν ο runner της σταματήσει να ανανεώνει τη μίσθωση, ή την αποδεσμεύσει κατά το stop(). Ένας υγιής runner δεν αντικαθίσταται ποτέ.

Όχι δύο ζωντανές εκτελέσεις της ίδιας εργασίας

Ενότητα με τίτλο «Όχι δύο ζωντανές εκτελέσεις της ίδιας εργασίας»

Backends μίσθωσης, overlap: 'skip'. Μια νέα εκτέλεση μιας εργασίας δεν ξεκινά όσο μια προηγούμενη εκτέλεση της ίδιας εργασίας διατηρεί ενεργή μίσθωση.

Μια εκτέλεση που κατέρρευσε ξεκινά ξανά

Ενότητα με τίτλο «Μια εκτέλεση που κατέρρευσε ξεκινά ξανά»

Backends μίσθωσης. Εάν ο runner καταρρεύσει, ένα εφεδρικό instance ξεκινά την εκτέλεση ξανά, έως και maxAttempts φορές, εκτός εάν ξεκινήσει πρώτα μια νεότερη εκτέλεση της εργασίας.

Οι παρωχημένες εκτελέσεις δεν ξεκινούν

Ενότητα με τίτλο «Οι παρωχημένες εκτελέσεις δεν ξεκινούν»

Μια εκτέλεση δεν ξεκινά περισσότερο από maxLateMs μετά την ώρα της. Σε backends μίσθωσης αυτό ελέγχεται επίσης στο ρολόι του store, επομένως μια καθυστερημένη ή σε ουρά διεκδίκηση δεν μπορεί να ξεκινήσει καμία.

Ένας παλιός runner δεν μπορεί να αλλάξει μια εκτέλεση

Ενότητα με τίτλο «Ένας παλιός runner δεν μπορεί να αλλάξει μια εκτέλεση»

Backends μίσθωσης. Μετά από μια ανάληψη, τα heartbeats και το “finished” του προηγούμενου runner απορρίπτονται.

Το anarchon παρέχει εκτέλεση τουλάχιστον μία φορά για εκτελέσεις των οποίων ο runner καταρρέει. Ένας handler μπορεί να εκτελεστεί περισσότερες από μία φορές για την ίδια θυρίδα:

  • Ένας runner καταρρέει αφού ολοκληρώσει τη μισή δουλειά. Η Προσπάθεια 2 ξεκινά από την αρχή.
  • Ένας runner παγώνει, ένα εφεδρικό instance αναλαμβάνει, και ο παγωμένος runner ξυπνά και εκτελείται λίγο ακόμα πριν μάθει ότι έχασε την εκτέλεση.

Κανένας scheduler δεν μπορεί να παρέχει εκτέλεση ακριβώς μία φορά για αυθαίρετο κώδικα: το σύστημα δεν μπορεί να διακρίνει το «η δουλειά έγινε και η επιβεβαίωση χάθηκε» από το «η δουλειά δεν έγινε». Αυτό που μπορεί να κάνει είναι να σας δώσει ένα σταθερό κλειδί, το ctx.slot, ώστε να μπορείτε να κάνετε την εργασία idempotent.

Τι να κάνετε: κάντε τις εργασίες ασφαλείς για διπλή εκτέλεση.

Μια εκτέλεση μπορεί να παραλειφθεί, βάσει σχεδιασμού:

Πότε Γιατί
Η προηγούμενη εκτέλεση βρίσκεται ακόμη σε εξέλιξη overlap: 'skip'
Κάθε instance ήταν εκτός λειτουργίας για περισσότερο από catchUpMs Η ανάκτηση καλύπτει μόνο την πιο πρόσφατη εκτέλεση, και μόνο εάν είναι πρόσφατη
Το instance καθυστέρησε υπερβολικά maxLateMs
Ένας runner κατέρρευσε και μια νεότερη εκτέλεση της εργασίας ξεκίνησε πρώτη Η νεότερη εκτέλεση την αντικαθιστά
Το store δεν ήταν προσβάσιμο όταν έπρεπε να τρέξει η εκτέλεση Κανείς δεν μπόρεσε να τη διεκδικήσει. Τα instances που τρέχουν περιμένουν την επόμενη θυρίδα· σε backend μίσθωσης ανακτάται μόνο εάν ένα instance ξεκινήσει εντός catchUpMs αφού επανέλθει το store

Το anarchon δεν είναι ουρά εργασιών προς εκτέλεση. Εάν κάθε θυρίδα πρέπει να παραγάγει αποτέλεσμα, ρυθμίστε κάθε εκτέλεση να δουλεύει σε ό,τι εκκρεμεί αντί μόνο στη «δική της» θυρίδα:

scheduler.every('send-pending-emails', '1m', async (ctx) => {
for (const email of await pendingEmails()) { // ό,τι εκκρεμεί
ctx.signal.throwIfAborted();
await send(email);
}
});

Μια εκτέλεση που παραλείπεται κοστίζει τότε μόνο μια καθυστέρηση, όχι χαμένη δουλειά.

Μια εκτέλεση ξεκινά μεταξύ της θυρίδας της και του slot + jitterMs, σε κάποιο instance. Μην βασίζεστε στο ότι η εκκίνηση θα είναι ακριβής· χρησιμοποιήστε το ctx.scheduledAt για την ονομαστική ώρα.

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

Οτιδήποτε σε backends μόνο-διεκδίκησης πέρα από τη διεκδίκηση

Ενότητα με τίτλο «Οτιδήποτε σε backends μόνο-διεκδίκησης πέρα από τη διεκδίκηση»

Με το RabbitMQ, το NATS και τις στρατηγικές Redis stream-id, idmp και script, μια εκτέλεση της οποίας ο runner καταρρέει είναι χαμένη. Δεν υπάρχουν εφεδρικά instances, καμία προστασία αλληλεπικάλυψης και καμία ανάκτηση.

Το anarchon είναι τόσο αξιόπιστο όσο το store του.

Εάν το store Τότε
Δεν είναι προσβάσιμο Καμία εκτέλεση δεν μπορεί να διεκδικηθεί. Οι εκτελούμενες εργασίες συνεχίζονται, αλλά δεν μπορούν να ανανεώσουν: μετά από leaseMs θεωρούνται νεκρές
Χάνει επιβεβαιωμένες εγγραφές (ασύγχρονος συγχρονισμός με failover) Μια διεκδικημένη εκτέλεση μπορεί να φαίνεται αδέσμευτη, και να εκτελεστεί ξανά
Έχει πολλαπλά primaries που δέχονται τις ίδιες εγγραφές Η ατομική διεκδίκηση παύει να ισχύει

Χρησιμοποιήστε ένα store ρυθμισμένο για ανθεκτικότητα κατάλληλη για τις εργασίες σας. Για εργασίες όπου ένα σπάνιο διπλότυπο είναι απαράδεκτο, η πραγματική προστασία είναι το idempotency στον handler, όχι το store.

Υπόθεση Εάν δεν ισχύει
Τα ρολόγια των instances συμφωνούν εντός περίπου ενός δευτερολέπτου Το πιο γρήγορο instance κερδίζει συχνότερα. Τίποτα δεν χαλάει
Τα ρολόγια συμφωνούν εντός maxLateMs Ένα instance πολύ πίσω βλέπει τις εκτελέσεις του να απορρίπτονται ως καθυστερημένες· ένα πολύ μπροστά διεκδικεί νωρίς
Το ρολόι του store κινείται σταθερά προς τα εμπρός Οι μισθώσεις υπολογίζονται πάνω σε αυτό. Ένα άλμα προς τα εμπρός μπορεί να λήξει τις μισθώσεις πρόωρα

Εκτελέστε NTP ή το συγχρονισμό ώρας της πλατφόρμας σας.

Όριο Τιμή
Μικρότερο μεσοδιάστημα 1 δευτερόλεπτο
Όριο history() 1 έως 10000
maxAttempts 1 έως 100
Αποθηκευμένο μήνυμα σφάλματος 2000 χαρακτήρες
Μεγαλύτερο εσωτερικό χρονόμετρο 1 λεπτό, μετά το ρολόι διαβάζεται ξανά
Node.js 20 ή νεότερο

Ένας handler γραμμένος για αυτές τις εγγυήσεις

Ενότητα με τίτλο «Ένας handler γραμμένος για αυτές τις εγγυήσεις»
scheduler.every(
'settle-payments',
'5m',
async (ctx) => {
// Το ίδιο παράθυρο σε κάθε προσπάθεια
const to = ctx.slot;
const from = to - 5 * 60_000;
for (const payment of await unsettledBetween(from, to)) {
ctx.signal.throwIfAborted(); // διακοπή όταν ζητηθεί
await settle(payment, { idempotencyKey: payment.id }); // ασφαλές να επαναληφθεί
}
},
{
timeoutMs: 4 * 60_000, // δεν μπορεί να κολλήσει για πάντα
maxAttempts: 3, // επαναλαμβάνεται εάν ο runner καταρρεύσει
},
);
  • Τα rolling deploys μέσα σε μία major έκδοση υποστηρίζονται. Instances σε δύο εκδόσεις της ίδιας major έκδοσης μπορούν να μοιράζονται ένα store όσο απλώνεται η νέα: τα δεδομένα μόνο επεκτείνονται, και κάθε έκδοση κρατά τα πεδία που δεν γνωρίζει.
  • Το store καταγράφει τη μορφή του. Αν μια έκδοση χρειαστεί ποτέ να αλλάξει τη διάταξη με τρόπο που οι παλαιότερες εκδόσεις δεν μπορούν να διαβάσουν, το changelog της το αναφέρει, όλα τα instances πρέπει να σταματήσουν πριν από την αναβάθμιση, και ένα παλαιότερο instance που συναντά τα νέα δεδομένα αρνείται να ξεκινήσει (ANARCHON_STORE_FORMAT) αντί να τα διαβάσει λάθος.
  • Τρέξτε μία έκδοση σε όλα τα instances μιας υπηρεσίας όταν ολοκληρωθεί το deploy. Οι χρόνοι εκτέλεσης προκύπτουν από τη βιβλιοθήκη cron από την οποία εξαρτάται το anarchon, και τα instances πρέπει να υπολογίζουν τους ίδιους.