Bootcamp i FastAPI-backendudvikling · Lektion

Planlagte og periodiske jobs med Celery Beat

Kør tilbagevendende jobs med Celery Beat, og koordinér cron-lignende planer sikkert på tværs af flere workers.

Lektion 4 af 413 trin

Planlagte og periodiske jobs med Celery Beat er en gratis Bootcamp i FastAPI-backendudvikling-lektion på CoddyKit. Dette er lektion 4 af 4. Du kan læse alle 3 lektioner i dette læringsspor gratis i deres fulde længde — derefter låser CoddyKit PRO alle lektioner op samt praktiske øvelser med en indbygget kodeeditor og en AI-underviser døgnet rundt. Den er en del af læringsforløbet i Bootcamp i FastAPI-backendudvikling, og dine fremskridt synkroniseres på tværs af nettet og CoddyKit-appen. Bootcamp i FastAPI-backendudvikling-kurset indeholder 4 lektioner i alt.

Hvorfor Celery Beat?

Din FastAPI-app kan uden problemer starte enkeltstående baggrundsopgaver med Celery, men noget arbejde skal køre efter en tidsplan: send en opsummeringsmail hver morgen, udløb forladte indkøbskurve hvert 10. minut, eller genberegn analyser hver nat.

Celery Beat er en planlægningsproces. Den udfører ikke selv opgaver — den vågner ved hvert tidsinterval, afgør hvilke opgaver der skal køres, og lægger dem på brokeren (Redis/RabbitMQ). Dine normale celery worker-processer henter dem og kører dem.

  • Beat = uret, der udgiver opgaver, som skal køres.
  • Worker = den kraft, der kører dem.

Denne adskillelse er hele grunden til, at periodiske job kan skaleres: én Beat, mange workers.

Definering af tidsplanen

Tidsplaner ligger på Celery-appen under conf.beat_schedule. Hver post knytter et navn til en ordbog med stien til opgaven, en tidsplan (sekunder, timedelta eller en crontab) og valgfrie args/kwargs.

Den enkleste tidsplan er et fast interval. Herunder kører oprydningsopgaven hvert 30. sekund. Strengen med opgavens sti skal stemme nøjagtigt overens med workerens registrerede navn.

from celery import Celery
from datetime import timedelta

app = Celery("jobs", broker="redis://localhost:6379/0")

@app.task(name="tasks.cleanup_sessions")
def cleanup_sessions():
    # delete expired sessions from the DB
    return "cleaned"

app.conf.beat_schedule = {
    "cleanup-every-30s": {
        "task": "tasks.cleanup_sessions",
        "schedule": timedelta(seconds=30),
    },
}

Crontab-tidsplaner med crontab()

Faste intervaller er grove. Til egentlig kalenderlogik — "hver hverdag kl. 07:30" — skal du bruge celery.schedules.crontab. Den afspejler Unix-crons felter: minute, hour, day_of_week, day_of_month, month_of_year.

  • crontab(minute=0, hour=0) — midnat hver dag.
  • crontab(minute="*/15") — hvert 15. minut.
  • crontab(hour=7, minute=30, day_of_week="1-5") — kl. 07:30 mandag–fredag.

Felter, der ikke er angivet, får som standard værdien * (alle værdier), præcis som en crontab-linje.

from celery.schedules import crontab

app.conf.beat_schedule = {
    "morning-digest": {
        "task": "tasks.send_digest",
        "schedule": crontab(hour=7, minute=30, day_of_week="1-5"),
    },
    "quarter-hour-sync": {
        "task": "tasks.sync_inventory",
        "schedule": crontab(minute="*/15"),
    },
}

Angivelse af argumenter og indstillinger pr. post

Hver tidsplanpost kan indeholde argumenter og indstillinger for det enkelte kald. Brug args (positionsargumenter) eller kwargs (navngivne argumenter) til at konfigurere den samme opgave forskelligt i forskellige poster. Ordbogen options lader dig sende en periodisk opgave til en bestemt queue, angive expires eller tilsidesætte prioriteten.

expires er vigtigt for periodiske job: Hvis Beat lægger en opgave i kø, men workers er overbelastede, kasseres en udløbet meddelelse i stedet for at blive kørt for sent og hobe sig op.

from celery.schedules import crontab

app.conf.beat_schedule = {
    "warm-cache-eu": {
        "task": "tasks.warm_cache",
        "schedule": crontab(minute="*/5"),
        "kwargs": {"region": "eu-west"},
        "options": {"queue": "cache", "expires": 120},
    },
    "warm-cache-us": {
        "task": "tasks.warm_cache",
        "schedule": crontab(minute="*/5"),
        "kwargs": {"region": "us-east"},
        "options": {"queue": "cache", "expires": 120},
    },
}

Registrering af tidsplaner med en dekorator

I stedet for én stor ordbog med beat_schedule kan du registrere poster tæt på opgaverne ved hjælp af signalet on_after_configure og app.add_periodic_task. Det holder tidsplanen sammen med den kode, den udløser, og undgår forældede strengstier.

add_periodic_task(schedule, signature, name=...) tager først intervallet eller crontab'en og derefter opgavesignaturen. Du kan sende task.s(arg) for at indbygge argumenter.

from celery import Celery
from celery.schedules import crontab

app = Celery("jobs", broker="redis://localhost:6379/0")

@app.task
def rotate_logs(target):
    return f"rotated {target}"

@app.on_after_configure.connect
def setup_periodic_tasks(sender, **kwargs):
    sender.add_periodic_task(
        crontab(hour=3, minute=0),
        rotate_logs.s("app.log"),
        name="nightly-log-rotation",
    )

Tidszoner: fejl nummer 1 i tidsplaner

Som standard fortolker Celery crontab-tider i UTC. Hvis du skriver crontab(hour=7) i forventning om lokal tid kl. 07:00, bliver opgaven kørt på det forkerte klokkeslæt. Angiv altid tidszonen eksplicit, og vælg den bevidst.

  • timezone — den zone, som crontab-tider evalueres i.
  • enable_utc=True — behold interne tidsstempler i UTC (anbefalet), mens crontab'er stadig evalueres i den valgte timezone.

Fastlås tidszonen i konfigurationen, så alle udviklere og servere er enige, uanset værtens lokale TZ.

app.conf.update(
    timezone="Europe/Istanbul",
    enable_utc=True,
)

# crontab(hour=9, minute=0) now means 09:00 Europe/Istanbul,
# stored/transmitted internally as UTC.

Kørsel af Beat og den permanente tidsplansfil

Du starter planlæggeren som sin egen proces. Standardplanlæggeren gemmer tilstanden for "seneste kørsel" i en lille fil, så den ikke kører alt igen efter en genstart.

  • celery -A app beat -l info — kør Beat.
  • --schedule /var/run/celerybeat-schedule — hvor shelve-tilstandsfilen ligger.
  • Du kan køre worker og Beat sammen under udvikling med celery -A app worker -B, men brug aldrig -B i produktion — det binder uret til én workers livscyklus.

I produktion skal du køre præcis én Beat-proces. To Beats betyder, at alle periodiske opgaver køres to gange.

Reglen om én planlægger på tværs af workers

Du kan skalere workers vandret til dusinvis af pods, og Celery håndterer det: brokeren fordeler hver meddelelse i køen til præcis én worker. Men Beat er uret, og du må kun køre ét ur.

Hvis to Beat-processer kører, afgør hver af dem uafhængigt, at en opgave skal køres, og udgiver den derfor, så abonnenter ser dublerede kørsler. Almindelige måder, det sker ved et uheld:

  • To replikaer af en pod, som begge starter beat.
  • Brug af worker -B, hvorefter worker-installationen skaleres til 2 eller flere replikaer.

Løsning: en dedikeret Beat-Deployment med replicas: 1, adskilt fra den worker-Deployment, du skalerer frit.

Idempotens: design opgaver, så de overlever dubletter

Selv med én Beat opstår der dubletter — en Beat-genstart på det forkerte sekund, en levering igen fra en broker, der leverer mindst én gang, eller en operatørfejl. Det robuste forsvar er at gøre periodiske opgaver idempotente: at køre dem to gange har samme effekt som at køre dem én gang.

Et enkelt mønster er en kortvarig distribueret lås i Redis ved hjælp af SET NX med en udløbstid. Den, der får låsen, udfører arbejdet; samtidige eller dublerede kørsler springes sikkert over.

import redis

r = redis.Redis(host="localhost", port=6379, db=0)

@app.task(name="tasks.charge_subscriptions")
def charge_subscriptions():
    # acquire a lock valid for 300s; only one runner proceeds
    got = r.set("lock:charge_subscriptions", "1", nx=True, ex=300)
    if not got:
        return "skipped: already running"
    try:
        # ... perform the billing run exactly once ...
        return "charged"
    finally:
        r.delete("lock:charge_subscriptions")

Databaseunderstøttede tidsplaner med django-celery-beat / RedBeat

Standardfilplanlæggeren betyder, at ændring af en tidsplan kræver redigering af kode og genstart af Beat. Til dynamiske tidsplaner har du brug for et backend-lager, som du kan redigere under kørsel.

  • RedBeat — gemmer tidsplaner i Redis; velegnet til en Redis-baseret FastAPI-stack. Angiv beat_scheduler = "redbeat.RedBeatScheduler".
  • django-celery-beat — gemmer poster i SQL-tabeller, som kan redigeres via en administrationsgrænseflade.

RedBeat leverer også en Redis-baseret lås, så hvis du ved en fejl starter to Beats, er det kun én, der er den aktive planlægger — et sikkerhedsnet for reglen om ét ur.

app.conf.update(
    redbeat_redis_url="redis://localhost:6379/1",
    beat_scheduler="redbeat.RedBeatScheduler",
    beat_max_loop_interval=5,
)

En selvstændig kontrol af, om en cron-opgave skal køres

For at opbygge en intuition for, hvad Beat gør ved hvert tidsinterval, får du her en lille selvstændig simulering: Givet en liste med cron-lignende job (interval i sekunder og et tidsstempel for seneste kørsel) afgør den, hvilke der skal køres nu. Dette er den centrale løkke, Beat udfører — sammenlign "nu" med "næste kørsel" — uden en broker.

def due_jobs(now, jobs):
    fired = []
    for name, interval, last_run in jobs:
        if now - last_run >= interval:
            fired.append(name)
    return fired

jobs = [
    ("cleanup", 30, 0),
    ("digest", 3600, 3500),
    ("sync", 900, 100),
]

now = 1000
print(due_jobs(now, jobs))  # ['cleanup', 'sync']

Hurtigt tjek: undgå dublerede periodiske kørsler

Du udruller en FastAPI- og Celery-stack til Kubernetes. For at håndtere belastningen skalerer du din worker-Deployment til 4 replikaer, og hver worker startes med celery -A app worker -B. Periodiske opgaver begynder at køre 4 gange hver. Hvad er den korrekte løsning?

Opsummering

Du kan nu planlægge tilbagevendende arbejde med Celery Beat:

  • Beat planlægger, workers udfører — Beat publicerer forfaldne opgaver til message broker'en; én Beat-instans og mange workers.
  • Planer findes i beat_schedule eller via add_periodic_task; brug timedelta til intervaller og crontab() til kalenderlogik.
  • Argumenter/indstillinger pr. post parametriserer opgaver og indstiller queue/expires for at undgå ophobning af forældede opgaver.
  • Tidszoner er som standard UTC — angiv timezone og enable_utc eksplicit.
  • Kun ét ur — brug aldrig worker -B i produktion; kør én dedikeret Beat-replika.
  • Idempotens (Redis-SET NX-låse) beskytter mod duplikerede eller genleverede kørsler.
  • RedBeat / django-celery-beat giver redigerbare, persistente og låsebeskyttede planer under kørsel.
Gratis at komme i gang

Lær Bootcamp i FastAPI-backendudvikling med en AI-underviser — gratis

Skriv og kør rigtig kode i din browser, få øjeblikkelig hjælp fra en AI-underviser døgnet rundt, og fortsæt, hvor du slap, på web eller i appen.

Kurser
21
Lektioner
84

Ofte stillede spørgsmål

Er lektionen “Planlagte og periodiske jobs med Celery Beat” gratis?

Ja — alle 3 lektioner i læringssporet Bootcamp i FastAPI-backendudvikling, inklusive “Planlagte og periodiske jobs med Celery Beat”, kan læses gratis i deres fulde længde her på webstedet. Derefter låser CoddyKit PRO alle lektioner op samt interaktive øvelser med en indbygget kodeeditor og en AI-underviser døgnet rundt. Bootcamp i FastAPI-backendudvikling-kurset indeholder 4 lektioner i alt.

Hvad lærer jeg i “Planlagte og periodiske jobs med Celery Beat”?

Kør tilbagevendende jobs med Celery Beat, og koordinér cron-lignende planer sikkert på tværs af flere workers. Du øver dig i Bootcamp i FastAPI-backendudvikling med praktisk kode, som du kører direkte i browseren, og en AI-vejleder døgnet rundt besvarer dine spørgsmål, mens du arbejder dig gennem lektionen.

Skal jeg have erfaring for at begynde på Bootcamp i FastAPI-backendudvikling?

Der kræves ingen tidligere erfaring. Bootcamp i FastAPI-backendudvikling på CoddyKit er tilrettelagt for både begyndere og øvede, så du kan starte her eller fra begyndelsen og lære i dit eget tempo. Dette er lektion 4 af 4.

Hvor lang tid tager lektionen “Planlagte og periodiske jobs med Celery Beat”?

De fleste CoddyKit-lektioner tager cirka 5–10 minutter. Hver lektion er kort og interaktiv, så du gør løbende fremskridt og kan fortsætte, hvor du slap – på både web og app.

Kan jeg skrive og køre kode i denne Bootcamp i FastAPI-backendudvikling-lektion?

Ja. Alle Bootcamp i FastAPI-backendudvikling-lektioner har en indbygget kodeeditor, så du kan skrive og køre rigtig kode direkte i din browser og få øjeblikkelig feedback fra AI – uden lokal opsætning.

Alle lektioner i dette kursus

  1. Letvægtsaflastning med BackgroundTasks
  2. Forbindelse af Celery-workers til en FastAPI-app
  3. Retries, idempotens og dead-letter-håndtering
  4. Planlagte og periodiske jobs med Celery Beat
← Tilbage til Bootcamp i FastAPI-backendudvikling